@vue-skuilder/db 0.1.4 → 0.1.5
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/dist/SyncStrategy-DnJRj-Xp.d.mts +74 -0
- package/dist/SyncStrategy-DnJRj-Xp.d.ts +74 -0
- package/dist/core/index.d.mts +90 -2
- package/dist/core/index.d.ts +90 -2
- package/dist/core/index.js +798 -650
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +769 -621
- package/dist/core/index.mjs.map +1 -1
- package/dist/dataLayerProvider-B8wquRiB.d.mts +37 -0
- package/dist/dataLayerProvider-DRjMZMaf.d.ts +37 -0
- package/dist/impl/couch/index.d.mts +292 -0
- package/dist/impl/couch/index.d.ts +292 -0
- package/dist/impl/couch/index.js +8522 -0
- package/dist/impl/couch/index.js.map +1 -0
- package/dist/impl/couch/index.mjs +8474 -0
- package/dist/impl/couch/index.mjs.map +1 -0
- package/dist/impl/static/index.d.mts +204 -0
- package/dist/impl/static/index.d.ts +204 -0
- package/dist/impl/static/index.js +8499 -0
- package/dist/impl/static/index.js.map +1 -0
- package/dist/impl/static/index.mjs +8488 -0
- package/dist/impl/static/index.mjs.map +1 -0
- package/dist/index.d.mts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +3280 -2108
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3290 -2118
- package/dist/index.mjs.map +1 -1
- package/dist/types-B0GJsjOr.d.ts +47 -0
- package/dist/types-DIgj8pP7.d.mts +47 -0
- package/dist/types-legacy-CTsJvvxI.d.mts +137 -0
- package/dist/types-legacy-CTsJvvxI.d.ts +137 -0
- package/dist/{index-QMtzQI65.d.mts → userDB-C5dcuRZs.d.ts} +3 -251
- package/dist/{index-QMtzQI65.d.ts → userDB-ZSwOXiYN.d.mts} +3 -251
- package/dist/util/packer/index.d.mts +25 -0
- package/dist/util/packer/index.d.ts +25 -0
- package/dist/util/packer/index.js +307 -0
- package/dist/util/packer/index.js.map +1 -0
- package/dist/util/packer/index.mjs +280 -0
- package/dist/util/packer/index.mjs.map +1 -0
- package/package.json +12 -2
- package/src/core/interfaces/contentSource.ts +8 -6
- package/src/core/interfaces/userDB.ts +2 -2
- package/src/factory.ts +10 -7
- package/src/impl/{pouch/userDB.ts → common/BaseUserDB.ts} +225 -260
- package/src/impl/common/SyncStrategy.ts +90 -0
- package/src/impl/common/index.ts +23 -0
- package/src/impl/common/types.ts +50 -0
- package/src/impl/common/userDBHelpers.ts +144 -0
- package/src/impl/couch/CouchDBSyncStrategy.ts +209 -0
- package/src/impl/{pouch → couch}/PouchDataLayerProvider.ts +12 -7
- package/src/impl/{pouch → couch}/adminDB.ts +3 -3
- package/src/impl/{pouch → couch}/auth.ts +2 -2
- package/src/impl/{pouch → couch}/classroomDB.ts +6 -6
- package/src/impl/{pouch → couch}/courseAPI.ts +28 -5
- package/src/impl/{pouch → couch}/courseDB.ts +24 -10
- package/src/impl/{pouch → couch}/courseLookupDB.ts +1 -1
- package/src/impl/{pouch → couch}/index.ts +27 -20
- package/src/impl/{pouch → couch}/updateQueue.ts +5 -1
- package/src/impl/{pouch → couch}/user-course-relDB.ts +6 -1
- package/src/impl/static/NoOpSyncStrategy.ts +70 -0
- package/src/impl/static/StaticDataLayerProvider.ts +89 -0
- package/src/impl/static/StaticDataUnpacker.ts +376 -0
- package/src/impl/static/courseDB.ts +257 -0
- package/src/impl/static/coursesDB.ts +37 -0
- package/src/impl/static/index.ts +8 -0
- package/src/impl/static/userDB.ts +179 -0
- package/src/index.ts +1 -1
- package/src/study/SessionController.ts +4 -4
- package/src/study/SpacedRepetition.ts +3 -3
- package/src/study/getCardDataShape.ts +2 -2
- package/src/util/index.ts +1 -0
- package/src/util/packer/CouchDBToStaticPacker.ts +349 -0
- package/src/util/packer/index.ts +4 -0
- package/src/util/packer/types.ts +52 -0
- package/tsconfig.json +7 -10
- package/tsup.config.ts +5 -3
- /package/src/impl/{pouch → couch}/clientCache.ts +0 -0
- /package/src/impl/{pouch → couch}/pouchdb-setup.ts +0 -0
- /package/src/impl/{pouch → couch}/types.ts +0 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// packages/db/src/impl/static/userDB.ts
|
|
2
|
+
|
|
3
|
+
import { UserDBInterface } from '../../core/interfaces';
|
|
4
|
+
|
|
5
|
+
export class StaticUserDB implements UserDBInterface {
|
|
6
|
+
constructor(_prefix: string) {}
|
|
7
|
+
|
|
8
|
+
isLoggedIn(): boolean {
|
|
9
|
+
return false; // Always guest in static mode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getUsername(): string {
|
|
13
|
+
return 'Guest';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async createAccount(_username: string, _password: string): Promise<any> {
|
|
17
|
+
throw new Error('Cannot create accounts in static mode');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async login(_username: string, _password: string): Promise<any> {
|
|
21
|
+
throw new Error('Cannot login in static mode');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async logout(): Promise<any> {
|
|
25
|
+
return { ok: true };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async getConfig(): Promise<any> {
|
|
29
|
+
return {}; // Default empty config
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async setConfig(_config: any): Promise<void> {
|
|
33
|
+
// No-op in static mode
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async putCardRecord<T>(_record: T): Promise<any> {
|
|
37
|
+
throw new Error('Cannot record card interactions in static mode');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getSeenCards(_courseId?: string): Promise<string[]> {
|
|
41
|
+
return []; // No seen cards in static mode
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getActiveCards(): Promise<string[]> {
|
|
45
|
+
return []; // No active cards in static mode
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async registerForCourse(_courseId: string, _previewMode?: boolean): Promise<any> {
|
|
49
|
+
return { ok: true, id: 'static-registration', rev: '1-static' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async getCourseRegistrationsDoc(): Promise<any> {
|
|
53
|
+
return { courses: [] }; // Empty registrations
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async deregisterFromCourse(_courseId: string): Promise<any> {
|
|
57
|
+
return { ok: true };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async addScheduledCard(_card: any): Promise<any> {
|
|
61
|
+
throw new Error('Cannot schedule cards in static mode');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async removeScheduledCard(_qualifiedID: string): Promise<any> {
|
|
65
|
+
throw new Error('Cannot remove scheduled cards in static mode');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async getScheduledCards(): Promise<any[]> {
|
|
69
|
+
return []; // No scheduled cards in static mode
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async addActivityRecord(_record: any): Promise<any> {
|
|
73
|
+
throw new Error('Cannot add activity records in static mode');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async getActivityRecords(_courseId?: string): Promise<any[]> {
|
|
77
|
+
return []; // No activity records in static mode
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async setUserElo(_courseId: string, _elo: any): Promise<any> {
|
|
81
|
+
throw new Error('Cannot set user ELO in static mode');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getUserElo(_courseId: string): Promise<any> {
|
|
85
|
+
return { global: { score: 1000, count: 0 }, tags: {}, misc: {} };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async updateDocument(_doc: any): Promise<any> {
|
|
89
|
+
throw new Error('Cannot update documents in static mode');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async dropCourse(_courseId: string, _dropStatus?: string): Promise<any> {
|
|
93
|
+
return { ok: true };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getCourseRegDoc(_courseId: string): Promise<any> {
|
|
97
|
+
return { courseID: _courseId, status: 'active' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async getActiveCourses(): Promise<any[]> {
|
|
101
|
+
return []; // No active courses in static mode
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async getPendingReviews(_courseId?: string): Promise<any[]> {
|
|
105
|
+
return []; // No pending reviews in static mode
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async getScheduledReviewCount(_courseId: string): Promise<number> {
|
|
109
|
+
return 0; // No scheduled reviews in static mode
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getReviewstoDate(_date: any, _courseId?: string): Promise<any[]> {
|
|
113
|
+
return []; // No reviews in static mode
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async addCardToReviews(_card: any): Promise<any> {
|
|
117
|
+
throw new Error('Cannot add cards to reviews in static mode');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async removeCardFromReviews(_qualifiedID: string): Promise<any> {
|
|
121
|
+
throw new Error('Cannot remove cards from reviews in static mode');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async scheduleCardReview(_card: any): Promise<any> {
|
|
125
|
+
throw new Error('Cannot schedule card reviews in static mode');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async removeScheduledCardReview(_qualifiedID: string): Promise<any> {
|
|
129
|
+
throw new Error('Cannot remove scheduled card reviews in static mode');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async registerForClassroom(_classId: string, _type: string): Promise<any> {
|
|
133
|
+
throw new Error('Cannot register for classrooms in static mode');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async dropFromClassroom(_classId: string): Promise<any> {
|
|
137
|
+
throw new Error('Cannot drop from classrooms in static mode');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async getClassroomRegistrations(): Promise<any[]> {
|
|
141
|
+
return []; // No classroom registrations in static mode
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async syncUp(): Promise<any> {
|
|
145
|
+
return { ok: true }; // No sync needed in static mode
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async syncDown(): Promise<any> {
|
|
149
|
+
return { ok: true }; // No sync needed in static mode
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async replicateToRemote(): Promise<any> {
|
|
153
|
+
return { ok: true }; // No replication in static mode
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async replicateFromRemote(): Promise<any> {
|
|
157
|
+
return { ok: true }; // No replication in static mode
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async getUserClassrooms(): Promise<{ registrations: any[] }> {
|
|
161
|
+
return { registrations: [] }; // No classrooms in static mode
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async getActiveClasses(): Promise<any[]> {
|
|
165
|
+
return []; // No active classes in static mode
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async updateUserElo(_courseId: string, _elo: any): Promise<any> {
|
|
169
|
+
throw new Error('Cannot update user ELO in static mode');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getCourseInterface(_courseId: string): Promise<any> {
|
|
173
|
+
throw new Error('Cannot get course interface in static mode');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async update(_doc: any): Promise<any> {
|
|
177
|
+
throw new Error('Cannot update user documents in static mode');
|
|
178
|
+
}
|
|
179
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
StudySessionItem,
|
|
6
6
|
StudySessionNewItem,
|
|
7
7
|
StudySessionReviewItem,
|
|
8
|
-
} from '
|
|
8
|
+
} from '@db/impl/couch';
|
|
9
9
|
|
|
10
|
-
import { CardRecord } from '
|
|
11
|
-
import { Loggable } from '
|
|
12
|
-
import { ScheduledCard } from '
|
|
10
|
+
import { CardRecord } from '@db/core';
|
|
11
|
+
import { Loggable } from '@db/util';
|
|
12
|
+
import { ScheduledCard } from '@db/core/types/user';
|
|
13
13
|
|
|
14
14
|
function randomInt(min: number, max: number): number {
|
|
15
15
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CardHistory, CardRecord, QuestionRecord } from '
|
|
2
|
-
import { areQuestionRecords } from '
|
|
3
|
-
import { Update } from '
|
|
1
|
+
import { CardHistory, CardRecord, QuestionRecord } from '@db/core/types/types-legacy';
|
|
2
|
+
import { areQuestionRecords } from '@db/core/util';
|
|
3
|
+
import { Update } from '@db/impl/couch/updateQueue';
|
|
4
4
|
import moment from 'moment';
|
|
5
5
|
import { logger } from '../util/logger';
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { allCourses } from '@vue-skuilder/courses';
|
|
2
2
|
import { log, NameSpacer, CourseConfig, DataShape } from '@vue-skuilder/common';
|
|
3
|
-
import { CardData, DisplayableData } from '
|
|
4
|
-
import { getCourseDB } from '
|
|
3
|
+
import { CardData, DisplayableData } from '@db/core';
|
|
4
|
+
import { getCourseDB } from '@db/impl/couch/courseAPI';
|
|
5
5
|
|
|
6
6
|
export async function getCardDataShape(courseID: string, cardID: string) {
|
|
7
7
|
const dataShapes: DataShape[] = [];
|
package/src/util/index.ts
CHANGED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// packages/db/src/util/packer/CouchDBToStaticPacker.ts
|
|
2
|
+
|
|
3
|
+
import { CardData, DocType, Tag } from '../../core/types/types-legacy';
|
|
4
|
+
import { logger } from '../logger';
|
|
5
|
+
// CourseConfig interface - simplified for packer use
|
|
6
|
+
|
|
7
|
+
import { CourseConfig } from '@vue-skuilder/common';
|
|
8
|
+
import {
|
|
9
|
+
ChunkMetadata,
|
|
10
|
+
DesignDocument,
|
|
11
|
+
IndexMetadata,
|
|
12
|
+
PackedCourseData,
|
|
13
|
+
PackerConfig,
|
|
14
|
+
StaticCourseManifest,
|
|
15
|
+
} from './types';
|
|
16
|
+
|
|
17
|
+
export class CouchDBToStaticPacker {
|
|
18
|
+
private config: PackerConfig;
|
|
19
|
+
|
|
20
|
+
constructor(config: Partial<PackerConfig> = {}) {
|
|
21
|
+
this.config = {
|
|
22
|
+
chunkSize: 1000,
|
|
23
|
+
includeAttachments: true,
|
|
24
|
+
...config,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Pack a CouchDB course database into static data structures
|
|
30
|
+
*/
|
|
31
|
+
async packCourse(sourceDB: PouchDB.Database, courseId: string): Promise<PackedCourseData> {
|
|
32
|
+
logger.info(`Starting static pack for course: ${courseId}`);
|
|
33
|
+
|
|
34
|
+
const manifest: StaticCourseManifest = {
|
|
35
|
+
version: '1.0.0',
|
|
36
|
+
courseId,
|
|
37
|
+
courseName: '',
|
|
38
|
+
courseConfig: null,
|
|
39
|
+
lastUpdated: new Date().toISOString(),
|
|
40
|
+
documentCount: 0,
|
|
41
|
+
chunks: [],
|
|
42
|
+
indices: [],
|
|
43
|
+
designDocs: [],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// 1. Extract course config
|
|
47
|
+
const courseConfig = await this.extractCourseConfig(sourceDB);
|
|
48
|
+
manifest.courseName = courseConfig.name;
|
|
49
|
+
manifest.courseConfig = courseConfig;
|
|
50
|
+
|
|
51
|
+
// 2. Extract and process design documents
|
|
52
|
+
manifest.designDocs = await this.extractDesignDocs(sourceDB);
|
|
53
|
+
|
|
54
|
+
// 3. Extract all documents by type and create chunks
|
|
55
|
+
const docsByType = await this.extractDocumentsByType(sourceDB);
|
|
56
|
+
|
|
57
|
+
// 4. Create chunks and prepare chunk data
|
|
58
|
+
const chunks = new Map<string, any[]>();
|
|
59
|
+
for (const [docType, docs] of Object.entries(docsByType)) {
|
|
60
|
+
const chunkMetadata = this.createChunks(docs, docType as DocType);
|
|
61
|
+
manifest.chunks.push(...chunkMetadata);
|
|
62
|
+
manifest.documentCount += docs.length;
|
|
63
|
+
|
|
64
|
+
// Prepare chunk data
|
|
65
|
+
this.prepareChunkData(chunkMetadata, docs, chunks);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 5. Build indices
|
|
69
|
+
const indices = new Map<string, any>();
|
|
70
|
+
manifest.indices = await this.buildIndices(docsByType, manifest.designDocs, indices);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
manifest,
|
|
74
|
+
chunks,
|
|
75
|
+
indices,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private async extractCourseConfig(db: PouchDB.Database): Promise<CourseConfig> {
|
|
80
|
+
try {
|
|
81
|
+
return await db.get<CourseConfig>('CourseConfig');
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.error('Failed to extract course config:', error);
|
|
84
|
+
throw new Error('Course config not found');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private async extractDesignDocs(db: PouchDB.Database): Promise<DesignDocument[]> {
|
|
89
|
+
const result = await db.allDocs({
|
|
90
|
+
startkey: '_design/',
|
|
91
|
+
endkey: '_design/\ufff0',
|
|
92
|
+
include_docs: true,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return result.rows.map((row) => ({
|
|
96
|
+
_id: row.id,
|
|
97
|
+
views: (row.doc as any).views || {},
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async extractDocumentsByType(db: PouchDB.Database): Promise<Record<DocType, any[]>> {
|
|
102
|
+
const allDocs = await db.allDocs({ include_docs: true });
|
|
103
|
+
const docsByType: Record<string, any[]> = {};
|
|
104
|
+
|
|
105
|
+
for (const row of allDocs.rows) {
|
|
106
|
+
if (row.id.startsWith('_')) continue; // Skip design docs
|
|
107
|
+
|
|
108
|
+
const doc = row.doc as any;
|
|
109
|
+
if (doc.docType) {
|
|
110
|
+
if (!docsByType[doc.docType]) {
|
|
111
|
+
docsByType[doc.docType] = [];
|
|
112
|
+
}
|
|
113
|
+
docsByType[doc.docType].push(doc);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return docsByType as Record<DocType, any[]>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private createChunks(docs: any[], docType: DocType): ChunkMetadata[] {
|
|
121
|
+
const chunks: ChunkMetadata[] = [];
|
|
122
|
+
const sortedDocs = docs.sort((a, b) => a._id.localeCompare(b._id));
|
|
123
|
+
|
|
124
|
+
for (let i = 0; i < sortedDocs.length; i += this.config.chunkSize) {
|
|
125
|
+
const chunk = sortedDocs.slice(i, i + this.config.chunkSize);
|
|
126
|
+
const chunkId = `${docType}-${String(Math.floor(i / this.config.chunkSize)).padStart(4, '0')}`;
|
|
127
|
+
|
|
128
|
+
chunks.push({
|
|
129
|
+
id: chunkId,
|
|
130
|
+
docType,
|
|
131
|
+
startKey: chunk[0]._id,
|
|
132
|
+
endKey: chunk[chunk.length - 1]._id,
|
|
133
|
+
documentCount: chunk.length,
|
|
134
|
+
path: `chunks/${chunkId}.json`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return chunks;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private prepareChunkData(
|
|
142
|
+
chunkMetadata: ChunkMetadata[],
|
|
143
|
+
docs: any[],
|
|
144
|
+
chunks: Map<string, any[]>
|
|
145
|
+
): void {
|
|
146
|
+
const sortedDocs = docs.sort((a, b) => a._id.localeCompare(b._id));
|
|
147
|
+
|
|
148
|
+
for (const chunk of chunkMetadata) {
|
|
149
|
+
const chunkDocs = sortedDocs.filter(
|
|
150
|
+
(doc) => doc._id >= chunk.startKey && doc._id <= chunk.endKey
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Clean documents for storage
|
|
154
|
+
const cleanedDocs = chunkDocs.map((doc) => {
|
|
155
|
+
const cleaned = { ...doc };
|
|
156
|
+
delete cleaned._rev; // Remove revision info
|
|
157
|
+
if (!this.config.includeAttachments) {
|
|
158
|
+
delete cleaned._attachments;
|
|
159
|
+
}
|
|
160
|
+
return cleaned;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
chunks.set(chunk.id, cleanedDocs);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private async buildIndices(
|
|
168
|
+
docsByType: Record<DocType, any[]>,
|
|
169
|
+
designDocs: DesignDocument[],
|
|
170
|
+
indices: Map<string, any>
|
|
171
|
+
): Promise<IndexMetadata[]> {
|
|
172
|
+
const indexMetadata: IndexMetadata[] = [];
|
|
173
|
+
|
|
174
|
+
// Build ELO index
|
|
175
|
+
if (docsByType[DocType.CARD]) {
|
|
176
|
+
const eloIndexMeta = await this.buildEloIndex(
|
|
177
|
+
docsByType[DocType.CARD] as CardData[],
|
|
178
|
+
indices
|
|
179
|
+
);
|
|
180
|
+
indexMetadata.push(eloIndexMeta);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Build tag indices
|
|
184
|
+
if (docsByType[DocType.TAG]) {
|
|
185
|
+
const tagIndexMeta = await this.buildTagIndex(docsByType[DocType.TAG] as Tag[], indices);
|
|
186
|
+
indexMetadata.push(tagIndexMeta);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Build indices from design documents
|
|
190
|
+
for (const designDoc of designDocs) {
|
|
191
|
+
for (const [viewName, viewDef] of Object.entries(designDoc.views)) {
|
|
192
|
+
if (viewDef.map) {
|
|
193
|
+
const indexMeta = await this.buildViewIndex(
|
|
194
|
+
viewName,
|
|
195
|
+
viewDef.map,
|
|
196
|
+
docsByType,
|
|
197
|
+
indices,
|
|
198
|
+
viewDef.reduce
|
|
199
|
+
);
|
|
200
|
+
if (indexMeta) indexMetadata.push(indexMeta);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return indexMetadata;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async buildEloIndex(
|
|
209
|
+
cards: CardData[],
|
|
210
|
+
indices: Map<string, any>
|
|
211
|
+
): Promise<IndexMetadata> {
|
|
212
|
+
// Build a B-tree like structure for ELO queries
|
|
213
|
+
const eloIndex: Array<{ elo: number; cardId: string }> = [];
|
|
214
|
+
|
|
215
|
+
for (const card of cards) {
|
|
216
|
+
if (card.elo?.global?.score) {
|
|
217
|
+
eloIndex.push({
|
|
218
|
+
elo: card.elo.global.score,
|
|
219
|
+
cardId: (card as any)._id,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Sort by ELO for efficient range queries
|
|
225
|
+
eloIndex.sort((a, b) => a.elo - b.elo);
|
|
226
|
+
|
|
227
|
+
// Create buckets for faster lookup
|
|
228
|
+
const buckets: Record<number, string[]> = {};
|
|
229
|
+
const bucketSize = 50; // ELO points per bucket
|
|
230
|
+
|
|
231
|
+
for (const entry of eloIndex) {
|
|
232
|
+
const bucket = Math.floor(entry.elo / bucketSize) * bucketSize;
|
|
233
|
+
if (!buckets[bucket]) buckets[bucket] = [];
|
|
234
|
+
buckets[bucket].push(entry.cardId);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Store the index data
|
|
238
|
+
indices.set('elo', {
|
|
239
|
+
sorted: eloIndex,
|
|
240
|
+
buckets: buckets,
|
|
241
|
+
stats: {
|
|
242
|
+
min: eloIndex[0]?.elo || 0,
|
|
243
|
+
max: eloIndex[eloIndex.length - 1]?.elo || 0,
|
|
244
|
+
count: eloIndex.length,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
name: 'elo',
|
|
250
|
+
type: 'btree',
|
|
251
|
+
path: 'indices/elo.json',
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private async buildTagIndex(tags: Tag[], indices: Map<string, any>): Promise<IndexMetadata> {
|
|
256
|
+
// Build inverted index for tags
|
|
257
|
+
const tagIndex: Record<
|
|
258
|
+
string,
|
|
259
|
+
{
|
|
260
|
+
cardIds: string[];
|
|
261
|
+
snippet: string;
|
|
262
|
+
count: number;
|
|
263
|
+
}
|
|
264
|
+
> = {};
|
|
265
|
+
|
|
266
|
+
for (const tag of tags) {
|
|
267
|
+
tagIndex[tag.name] = {
|
|
268
|
+
cardIds: tag.taggedCards,
|
|
269
|
+
snippet: tag.snippet,
|
|
270
|
+
count: tag.taggedCards.length,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Also build a reverse index (card -> tags)
|
|
275
|
+
const cardToTags: Record<string, string[]> = {};
|
|
276
|
+
for (const tag of tags) {
|
|
277
|
+
for (const cardId of tag.taggedCards) {
|
|
278
|
+
if (!cardToTags[cardId]) cardToTags[cardId] = [];
|
|
279
|
+
cardToTags[cardId].push(tag.name);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
indices.set('tags', {
|
|
284
|
+
byTag: tagIndex,
|
|
285
|
+
byCard: cardToTags,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
name: 'tags',
|
|
290
|
+
type: 'hash',
|
|
291
|
+
path: 'indices/tags.json',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private async buildViewIndex(
|
|
296
|
+
viewName: string,
|
|
297
|
+
mapFunction: string,
|
|
298
|
+
docsByType: Record<DocType, any[]>,
|
|
299
|
+
indices: Map<string, any>,
|
|
300
|
+
_reduceFunction?: string
|
|
301
|
+
): Promise<IndexMetadata | null> {
|
|
302
|
+
try {
|
|
303
|
+
// Parse and execute the map function in a sandboxed way
|
|
304
|
+
// This is a simplified version - in production you'd want proper sandboxing
|
|
305
|
+
const viewResults: Array<{ key: any; value: any; id: string }> = [];
|
|
306
|
+
|
|
307
|
+
// Create a safe emit function
|
|
308
|
+
const emit = (key: any, value: any) => {
|
|
309
|
+
viewResults.push({ key, value, id: currentDocId });
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
let currentDocId = '';
|
|
313
|
+
|
|
314
|
+
// Create the map function
|
|
315
|
+
// Note: This is simplified and would need proper sandboxing in production
|
|
316
|
+
const mapFn = new Function('doc', 'emit', mapFunction);
|
|
317
|
+
|
|
318
|
+
// Run map function on all documents
|
|
319
|
+
for (const docs of Object.values(docsByType)) {
|
|
320
|
+
for (const doc of docs) {
|
|
321
|
+
currentDocId = doc._id;
|
|
322
|
+
try {
|
|
323
|
+
mapFn(doc, emit);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
logger.warn(`Map function error for doc ${doc._id}:`, error);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Sort by key for efficient querying
|
|
331
|
+
viewResults.sort((a, b) => {
|
|
332
|
+
if (a.key < b.key) return -1;
|
|
333
|
+
if (a.key > b.key) return 1;
|
|
334
|
+
return 0;
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
indices.set(`view-${viewName}`, viewResults);
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
name: `view-${viewName}`,
|
|
341
|
+
type: 'btree',
|
|
342
|
+
path: `indices/view-${viewName}.json`,
|
|
343
|
+
};
|
|
344
|
+
} catch (error) {
|
|
345
|
+
logger.error(`Failed to build index for view ${viewName}:`, error);
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// packages/db/src/util/packer/types.ts
|
|
2
|
+
|
|
3
|
+
import { CourseConfig } from '@vue-skuilder/common';
|
|
4
|
+
import { DocType } from '../../core/types/types-legacy';
|
|
5
|
+
|
|
6
|
+
export interface StaticCourseManifest {
|
|
7
|
+
version: string;
|
|
8
|
+
courseId: string;
|
|
9
|
+
courseName: string;
|
|
10
|
+
courseConfig: CourseConfig | null; // Full CourseConfig object
|
|
11
|
+
lastUpdated: string;
|
|
12
|
+
documentCount: number;
|
|
13
|
+
chunks: ChunkMetadata[];
|
|
14
|
+
indices: IndexMetadata[];
|
|
15
|
+
designDocs: DesignDocument[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ChunkMetadata {
|
|
19
|
+
id: string;
|
|
20
|
+
docType: DocType;
|
|
21
|
+
startKey: string;
|
|
22
|
+
endKey: string;
|
|
23
|
+
documentCount: number;
|
|
24
|
+
path: string; // Relative path for file writing
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface IndexMetadata {
|
|
28
|
+
name: string;
|
|
29
|
+
type: 'btree' | 'hash' | 'spatial';
|
|
30
|
+
path: string; // Relative path for file writing
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DesignDocument {
|
|
34
|
+
_id: string;
|
|
35
|
+
views: {
|
|
36
|
+
[viewName: string]: {
|
|
37
|
+
map: string;
|
|
38
|
+
reduce?: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface PackerConfig {
|
|
44
|
+
chunkSize: number;
|
|
45
|
+
includeAttachments: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PackedCourseData {
|
|
49
|
+
manifest: StaticCourseManifest;
|
|
50
|
+
chunks: Map<string, any[]>; // chunkId -> documents
|
|
51
|
+
indices: Map<string, any>; // indexName -> index data
|
|
52
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
"include": ["src"]
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"noEmit": true
|
|
7
|
+
},
|
|
8
|
+
"include": ["src"]
|
|
12
9
|
}
|
package/tsup.config.ts
CHANGED
|
@@ -4,7 +4,9 @@ export default defineConfig({
|
|
|
4
4
|
entry: [
|
|
5
5
|
'src/index.ts',
|
|
6
6
|
'src/core/index.ts',
|
|
7
|
-
'src/
|
|
7
|
+
'src/impl/couch/index.ts',
|
|
8
|
+
'src/impl/static/index.ts',
|
|
9
|
+
'src/util/packer/index.ts',
|
|
8
10
|
],
|
|
9
11
|
format: ['cjs', 'esm'],
|
|
10
12
|
dts: true,
|
|
@@ -12,6 +14,6 @@ export default defineConfig({
|
|
|
12
14
|
sourcemap: true,
|
|
13
15
|
clean: true,
|
|
14
16
|
outExtension: ({ format }) => ({
|
|
15
|
-
js: format === 'esm' ? '.mjs' : '.js'
|
|
16
|
-
})
|
|
17
|
+
js: format === 'esm' ? '.mjs' : '.js',
|
|
18
|
+
}),
|
|
17
19
|
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|