@vue-skuilder/db 0.1.7 → 0.1.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/dist/{SyncStrategy-DnJRj-Xp.d.mts → SyncStrategy-CyATpyLQ.d.mts} +6 -0
- package/dist/{SyncStrategy-DnJRj-Xp.d.ts → SyncStrategy-CyATpyLQ.d.ts} +6 -0
- package/dist/core/index.d.mts +5 -5
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +131 -118
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +128 -115
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BbW9EnZK.d.mts → dataLayerProvider-BInqI_RF.d.mts} +1 -1
- package/dist/{dataLayerProvider-6stCgDME.d.ts → dataLayerProvider-DqtNroSh.d.ts} +1 -1
- package/dist/impl/couch/index.d.mts +6 -6
- package/dist/impl/couch/index.d.ts +6 -6
- package/dist/impl/couch/index.js +1365 -1252
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +1359 -1246
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.mts +8 -6
- package/dist/impl/static/index.d.ts +8 -6
- package/dist/impl/static/index.js +253 -843
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +250 -842
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index-CLL31bEy.d.ts +137 -0
- package/dist/index-CUNnL38E.d.mts +137 -0
- package/dist/index.d.mts +10 -55
- package/dist/index.d.ts +10 -55
- package/dist/index.js +343 -170
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +340 -167
- package/dist/index.mjs.map +1 -1
- package/dist/{types-BvzcRAys.d.ts → types-BefDGkKa.d.ts} +1 -1
- package/dist/{types-CQQ80R5N.d.mts → types-DC-ckZug.d.mts} +1 -1
- package/dist/{types-legacy-CtrmkOLu.d.mts → types-legacy-Birv-Jx6.d.mts} +2 -2
- package/dist/{types-legacy-CtrmkOLu.d.ts → types-legacy-Birv-Jx6.d.ts} +2 -2
- package/dist/{userDB-DUY63VMN.d.ts → userDB-C33Hzjgn.d.mts} +10 -3
- package/dist/{userDB-7fM4tpgr.d.mts → userDB-DusL7OXe.d.ts} +10 -3
- package/dist/util/packer/index.d.mts +3 -63
- package/dist/util/packer/index.d.ts +3 -63
- package/dist/util/packer/index.js +53 -1
- package/dist/util/packer/index.js.map +1 -1
- package/dist/util/packer/index.mjs +53 -1
- package/dist/util/packer/index.mjs.map +1 -1
- package/package.json +7 -4
- package/src/core/types/types-legacy.ts +13 -1
- package/src/core/types/user.ts +9 -2
- package/src/core/util/index.ts +5 -4
- package/src/impl/common/BaseUserDB.ts +33 -22
- package/src/impl/common/SyncStrategy.ts +7 -0
- package/src/impl/common/index.ts +0 -1
- package/src/impl/common/userDBHelpers.ts +4 -4
- package/src/impl/couch/CouchDBSyncStrategy.ts +10 -0
- package/src/impl/couch/courseAPI.ts +7 -6
- package/src/impl/couch/index.ts +10 -5
- package/src/impl/couch/updateQueue.ts +12 -8
- package/src/impl/couch/user-course-relDB.ts +17 -27
- package/src/impl/static/NoOpSyncStrategy.ts +5 -0
- package/src/impl/static/StaticDataUnpacker.ts +18 -36
- package/src/impl/static/courseDB.ts +135 -17
- package/src/util/migrator/FileSystemAdapter.ts +20 -0
- package/src/util/migrator/StaticToCouchDBMigrator.ts +6 -0
- package/src/util/packer/CouchDBToStaticPacker.ts +92 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/util/logger.ts","../../../src/util/packer/CouchDBToStaticPacker.ts"],"sourcesContent":["/**\n * Simple logging utility for @vue-skuilder/db package\n *\n * This utility provides environment-aware logging with ESLint suppressions\n * to resolve console statement violations while maintaining logging functionality.\n */\n\nconst isDevelopment = typeof process !== 'undefined' && process.env.NODE_ENV === 'development';\nconst _isBrowser = typeof window !== 'undefined';\n\nexport const logger = {\n /**\n * Debug-level logging - only shown in development\n */\n debug: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.debug(`[DB:DEBUG] ${message}`, ...args);\n }\n },\n\n /**\n * Info-level logging - general information\n */\n info: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.info(`[DB:INFO] ${message}`, ...args);\n },\n\n /**\n * Warning-level logging - potential issues\n */\n warn: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.warn(`[DB:WARN] ${message}`, ...args);\n },\n\n /**\n * Error-level logging - serious problems\n */\n error: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.error(`[DB:ERROR] ${message}`, ...args);\n },\n\n /**\n * Log function for backward compatibility with existing log() usage\n */\n log: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.log(`[DB:LOG] ${message}`, ...args);\n }\n },\n};\n","// packages/db/src/util/packer/CouchDBToStaticPacker.ts\n\nimport { CardData, DocType, Tag } from '../../core/types/types-legacy';\nimport { logger } from '../logger';\n// CourseConfig interface - simplified for packer use\n\nimport { CourseConfig } from '@vue-skuilder/common';\nimport {\n ChunkMetadata,\n DesignDocument,\n IndexMetadata,\n PackedCourseData,\n PackerConfig,\n StaticCourseManifest,\n AttachmentData,\n} from './types';\n\nexport class CouchDBToStaticPacker {\n private config: PackerConfig;\n private sourceDB: PouchDB.Database | null = null;\n\n constructor(config: Partial<PackerConfig> = {}) {\n this.config = {\n chunkSize: 1000,\n includeAttachments: true,\n ...config,\n };\n }\n\n /**\n * Pack a CouchDB course database into static data structures\n */\n async packCourse(sourceDB: PouchDB.Database, courseId: string): Promise<PackedCourseData> {\n logger.info(`Starting static pack for course: ${courseId}`);\n this.sourceDB = sourceDB;\n\n const manifest: StaticCourseManifest = {\n version: '1.0.0',\n courseId,\n courseName: '',\n courseConfig: null,\n lastUpdated: new Date().toISOString(),\n documentCount: 0,\n chunks: [],\n indices: [],\n designDocs: [],\n };\n\n // 1. Extract course config\n const courseConfig = await this.extractCourseConfig(sourceDB);\n manifest.courseName = courseConfig.name;\n manifest.courseConfig = courseConfig;\n\n // 2. Extract and process design documents\n manifest.designDocs = await this.extractDesignDocs(sourceDB);\n\n // 3. Extract all documents by type and create chunks\n const docsByType = await this.extractDocumentsByType(sourceDB);\n\n // 4. Extract attachments if enabled\n const attachments = new Map<string, AttachmentData>();\n if (this.config.includeAttachments) {\n await this.extractAllAttachments(docsByType, attachments);\n }\n\n // 5. Create chunks and prepare chunk data\n const chunks = new Map<string, any[]>();\n for (const [docType, docs] of Object.entries(docsByType)) {\n const chunkMetadata = this.createChunks(docs, docType as DocType);\n manifest.chunks.push(...chunkMetadata);\n manifest.documentCount += docs.length;\n\n // Prepare chunk data\n this.prepareChunkData(chunkMetadata, docs, chunks);\n }\n\n // 6. Build indices\n const indices = new Map<string, any>();\n manifest.indices = await this.buildIndices(docsByType, manifest.designDocs, indices);\n\n return {\n manifest,\n chunks,\n indices,\n attachments,\n };\n }\n\n private async extractCourseConfig(db: PouchDB.Database): Promise<CourseConfig> {\n try {\n return await db.get<CourseConfig>('CourseConfig');\n } catch (error) {\n logger.error('Failed to extract course config:', error);\n throw new Error('Course config not found');\n }\n }\n\n private async extractDesignDocs(db: PouchDB.Database): Promise<DesignDocument[]> {\n const result = await db.allDocs({\n startkey: '_design/',\n endkey: '_design/\\ufff0',\n include_docs: true,\n });\n\n return result.rows.map((row) => ({\n _id: row.id,\n views: (row.doc as any).views || {},\n }));\n }\n\n private async extractDocumentsByType(db: PouchDB.Database): Promise<Record<DocType, any[]>> {\n const allDocs = await db.allDocs({ include_docs: true });\n const docsByType: Record<string, any[]> = {};\n\n for (const row of allDocs.rows) {\n if (row.id.startsWith('_')) continue; // Skip design docs\n\n const doc = row.doc as any;\n if (doc.docType) {\n if (!docsByType[doc.docType]) {\n docsByType[doc.docType] = [];\n }\n docsByType[doc.docType].push(doc);\n }\n }\n\n return docsByType as Record<DocType, any[]>;\n }\n\n private createChunks(docs: any[], docType: DocType): ChunkMetadata[] {\n const chunks: ChunkMetadata[] = [];\n const sortedDocs = docs.sort((a, b) => a._id.localeCompare(b._id));\n\n for (let i = 0; i < sortedDocs.length; i += this.config.chunkSize) {\n const chunk = sortedDocs.slice(i, i + this.config.chunkSize);\n const chunkId = `${docType}-${String(Math.floor(i / this.config.chunkSize)).padStart(4, '0')}`;\n\n chunks.push({\n id: chunkId,\n docType,\n startKey: chunk[0]._id,\n endKey: chunk[chunk.length - 1]._id,\n documentCount: chunk.length,\n path: `chunks/${chunkId}.json`,\n });\n }\n\n return chunks;\n }\n\n private prepareChunkData(\n chunkMetadata: ChunkMetadata[],\n docs: any[],\n chunks: Map<string, any[]>\n ): void {\n const sortedDocs = docs.sort((a, b) => a._id.localeCompare(b._id));\n\n for (const chunk of chunkMetadata) {\n const chunkDocs = sortedDocs.filter(\n (doc) => doc._id >= chunk.startKey && doc._id <= chunk.endKey\n );\n\n // Clean documents for storage\n const cleanedDocs = chunkDocs.map((doc) => {\n const cleaned = { ...doc };\n delete cleaned._rev; // Remove revision info\n \n if (this.config.includeAttachments && cleaned._attachments) {\n // Transform attachment stubs to file paths\n cleaned._attachments = this.transformAttachmentStubs(cleaned._attachments, cleaned._id);\n } else if (!this.config.includeAttachments) {\n delete cleaned._attachments;\n }\n \n return cleaned;\n });\n\n chunks.set(chunk.id, cleanedDocs);\n }\n }\n\n private async buildIndices(\n docsByType: Record<DocType, any[]>,\n designDocs: DesignDocument[],\n indices: Map<string, any>\n ): Promise<IndexMetadata[]> {\n const indexMetadata: IndexMetadata[] = [];\n\n // Build ELO index\n if (docsByType[DocType.CARD]) {\n const eloIndexMeta = await this.buildEloIndex(\n docsByType[DocType.CARD] as CardData[],\n indices\n );\n indexMetadata.push(eloIndexMeta);\n }\n\n // Build tag indices\n if (docsByType[DocType.TAG]) {\n const tagIndexMeta = await this.buildTagIndex(docsByType[DocType.TAG] as Tag[], indices);\n indexMetadata.push(tagIndexMeta);\n }\n\n // Build indices from design documents using CouchDB view queries\n for (const designDoc of designDocs) {\n for (const [viewName, viewDef] of Object.entries(designDoc.views)) {\n if (viewDef.map) {\n logger.info(`Processing view: ${designDoc._id}/${viewName}`);\n const indexMeta = await this.buildViewIndex(viewName, designDoc, indices);\n if (indexMeta) {\n indexMetadata.push(indexMeta);\n logger.info(`Successfully built index: ${indexMeta.name}`);\n } else {\n logger.warn(`Skipped view index: ${designDoc._id}/${viewName}`);\n }\n }\n }\n }\n\n return indexMetadata;\n }\n\n private async buildEloIndex(\n cards: CardData[],\n indices: Map<string, any>\n ): Promise<IndexMetadata> {\n // Build a B-tree like structure for ELO queries\n const eloIndex: Array<{ elo: number; cardId: string }> = [];\n\n for (const card of cards) {\n if (card.elo?.global?.score) {\n eloIndex.push({\n elo: card.elo.global.score,\n cardId: (card as any)._id,\n });\n }\n }\n\n // Sort by ELO for efficient range queries\n eloIndex.sort((a, b) => a.elo - b.elo);\n\n // Create buckets for faster lookup\n const buckets: Record<number, string[]> = {};\n const bucketSize = 50; // ELO points per bucket\n\n for (const entry of eloIndex) {\n const bucket = Math.floor(entry.elo / bucketSize) * bucketSize;\n if (!buckets[bucket]) buckets[bucket] = [];\n buckets[bucket].push(entry.cardId);\n }\n\n // Store the index data\n indices.set('elo', {\n sorted: eloIndex,\n buckets: buckets,\n stats: {\n min: eloIndex[0]?.elo || 0,\n max: eloIndex[eloIndex.length - 1]?.elo || 0,\n count: eloIndex.length,\n },\n });\n\n return {\n name: 'elo',\n type: 'btree',\n path: 'indices/elo.json',\n };\n }\n\n private async buildTagIndex(tags: Tag[], indices: Map<string, any>): Promise<IndexMetadata> {\n // Build inverted index for tags\n const tagIndex: Record<\n string,\n {\n cardIds: string[];\n snippet: string;\n count: number;\n }\n > = {};\n\n for (const tag of tags) {\n tagIndex[tag.name] = {\n cardIds: tag.taggedCards,\n snippet: tag.snippet,\n count: tag.taggedCards.length,\n };\n }\n\n // Also build a reverse index (card -> tags)\n const cardToTags: Record<string, string[]> = {};\n for (const tag of tags) {\n for (const cardId of tag.taggedCards) {\n if (!cardToTags[cardId]) cardToTags[cardId] = [];\n cardToTags[cardId].push(tag.name);\n }\n }\n\n indices.set('tags', {\n byTag: tagIndex,\n byCard: cardToTags,\n });\n\n return {\n name: 'tags',\n type: 'hash',\n path: 'indices/tags.json',\n };\n }\n\n /**\n * Build view index by querying CouchDB views directly instead of parsing map functions\n */\n private async buildViewIndex(\n viewName: string,\n designDoc: DesignDocument,\n indices: Map<string, any>\n ): Promise<IndexMetadata | null> {\n if (!this.sourceDB) {\n logger.error('Source database not available for view querying');\n return null;\n }\n\n try {\n const designDocId = designDoc._id; // e.g., \"_design/elo\"\n const viewPath = `${designDocId}/${viewName}`;\n \n logger.info(`Querying CouchDB view: ${viewPath}`);\n \n // Query the view directly from CouchDB\n const viewResults = await this.sourceDB.query(viewPath, {\n include_docs: false,\n });\n\n if (!viewResults.rows || viewResults.rows.length === 0) {\n logger.warn(`View ${viewPath} returned no results`);\n return null;\n }\n\n logger.info(`Successfully queried view ${viewPath}: ${viewResults.rows.length} results`);\n\n // Format the results for static consumption\n const formattedResults = this.formatViewResults(viewName, viewResults.rows, designDoc);\n \n const indexName = `view-${designDoc._id.replace('_design/', '')}-${viewName}`;\n indices.set(indexName, formattedResults);\n\n return {\n name: indexName,\n type: 'view',\n path: `indices/${indexName}.json`,\n };\n } catch (error) {\n logger.error(`Failed to query view ${designDoc._id}/${viewName}:`, error);\n // Return null to gracefully skip this view rather than failing the entire pack\n return null;\n }\n }\n\n /**\n * Format CouchDB view results for static consumption\n */\n private formatViewResults(\n viewName: string,\n viewRows: Array<{ key: any; value: any; id: string }>,\n designDoc: DesignDocument\n ): any {\n const baseResult = {\n type: 'couchdb-view',\n viewName,\n designDoc: designDoc._id,\n results: viewRows,\n metadata: {\n resultCount: viewRows.length,\n generatedAt: new Date().toISOString(),\n },\n };\n\n // Apply view-specific formatting\n switch (viewName) {\n case 'elo':\n return this.formatEloViewIndex(viewRows, baseResult);\n case 'getTags':\n return this.formatTagsViewIndex(viewRows, baseResult);\n case 'cardsByInexperience':\n return this.formatInexperienceViewIndex(viewRows, baseResult);\n default:\n return this.formatGenericViewIndex(viewRows, baseResult);\n }\n }\n\n /**\n * Format ELO view results - convert to sorted array format\n */\n private formatEloViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Sort by ELO score (key) for efficient range queries\n const sortedResults = viewRows.sort((a, b) => {\n if (typeof a.key === 'number' && typeof b.key === 'number') {\n return a.key - b.key;\n }\n return 0;\n });\n\n return {\n ...baseResult,\n sorted: sortedResults,\n stats: {\n min: sortedResults[0]?.key || 0,\n max: sortedResults[sortedResults.length - 1]?.key || 0,\n count: sortedResults.length,\n },\n };\n }\n\n /**\n * Format tags view results - convert to tag mapping format\n */\n private formatTagsViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Group by tag name (key)\n const tagMap: Record<string, string[]> = {};\n \n for (const row of viewRows) {\n const tagName = row.key;\n if (typeof tagName === 'string') {\n if (!tagMap[tagName]) {\n tagMap[tagName] = [];\n }\n tagMap[tagName].push(row.id);\n }\n }\n\n return {\n ...baseResult,\n byTag: tagMap,\n tagCount: Object.keys(tagMap).length,\n };\n }\n\n /**\n * Format inexperience view results - convert to experience-based format\n */\n private formatInexperienceViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Sort by inexperience level (key) - lower numbers = less experience\n const sortedResults = viewRows.sort((a, b) => {\n if (typeof a.key === 'number' && typeof b.key === 'number') {\n return a.key - b.key;\n }\n return 0;\n });\n\n return {\n ...baseResult,\n sorted: sortedResults,\n stats: {\n minInexperience: sortedResults[0]?.key || 0,\n maxInexperience: sortedResults[sortedResults.length - 1]?.key || 0,\n count: sortedResults.length,\n },\n };\n }\n\n /**\n * Format generic view results - fallback for unknown views\n */\n private formatGenericViewIndex(\n _viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n return {\n ...baseResult,\n // Keep results as-is for unknown view types\n };\n }\n\n /**\n * Extract all attachments from documents and download binary data\n */\n private async extractAllAttachments(\n docsByType: Record<DocType, any[]>,\n attachments: Map<string, AttachmentData>\n ): Promise<void> {\n logger.info('Extracting attachments...');\n \n const allDocs: any[] = [];\n for (const docs of Object.values(docsByType)) {\n allDocs.push(...docs);\n }\n\n const docsWithAttachments = allDocs.filter(doc => doc._attachments && Object.keys(doc._attachments).length > 0);\n \n if (docsWithAttachments.length === 0) {\n logger.info('No attachments found');\n return;\n }\n\n logger.info(`Found ${docsWithAttachments.length} documents with attachments`);\n\n // Process attachments concurrently\n const extractionPromises = docsWithAttachments.map(doc => \n this.extractDocumentAttachments(doc, attachments)\n );\n\n await Promise.all(extractionPromises);\n \n logger.info(`Extracted ${attachments.size} attachment files`);\n }\n\n /**\n * Extract attachments for a single document\n */\n private async extractDocumentAttachments(\n doc: any,\n attachments: Map<string, AttachmentData>\n ): Promise<void> {\n if (!doc._attachments || !this.sourceDB) {\n return;\n }\n\n const docId = doc._id;\n \n for (const [attachmentName, metadata] of Object.entries(doc._attachments as Record<string, any>)) {\n try {\n // Download attachment binary data\n const attachmentResponse = await this.sourceDB.getAttachment(docId, attachmentName);\n \n // Convert to buffer\n let buffer: Buffer;\n if (attachmentResponse instanceof ArrayBuffer) {\n buffer = Buffer.from(attachmentResponse);\n } else if (Buffer.isBuffer(attachmentResponse)) {\n buffer = attachmentResponse;\n } else {\n // For browser environments, the response might be a Blob\n const blob = attachmentResponse as Blob;\n buffer = Buffer.from(await blob.arrayBuffer());\n }\n\n // Generate filename with proper extension\n const extension = this.getFileExtension(metadata.content_type);\n const filename = `${attachmentName}${extension}`;\n const attachmentPath = `attachments/${docId}/${filename}`;\n \n // Store attachment data\n attachments.set(attachmentPath, {\n docId,\n attachmentName,\n filename,\n path: attachmentPath,\n contentType: metadata.content_type,\n length: metadata.length || buffer.length,\n digest: metadata.digest,\n buffer,\n });\n\n logger.debug(`Extracted attachment: ${attachmentPath}`);\n } catch (error) {\n logger.error(`Failed to extract attachment ${docId}/${attachmentName}:`, error);\n throw new Error(`Failed to extract attachment ${docId}/${attachmentName}: ${error}`);\n }\n }\n }\n\n /**\n * Transform attachment stubs to include file paths\n */\n private transformAttachmentStubs(\n attachments: Record<string, any>,\n docId: string\n ): Record<string, any> {\n const transformed: Record<string, any> = {};\n \n for (const [attachmentName, metadata] of Object.entries(attachments)) {\n const extension = this.getFileExtension(metadata.content_type);\n const filename = `${attachmentName}${extension}`;\n const attachmentPath = `attachments/${docId}/${filename}`;\n \n transformed[attachmentName] = {\n path: attachmentPath,\n content_type: metadata.content_type,\n length: metadata.length,\n digest: metadata.digest,\n stub: false, // No longer a stub - we have the actual file\n };\n }\n \n return transformed;\n }\n\n /**\n * Get file extension from content type\n */\n private getFileExtension(contentType: string): string {\n const extensionMap: Record<string, string> = {\n 'image/jpeg': '.jpg',\n 'image/jpg': '.jpg', \n 'image/png': '.png',\n 'image/gif': '.gif',\n 'image/webp': '.webp',\n 'audio/mpeg': '.mp3',\n 'audio/mp3': '.mp3',\n 'audio/wav': '.wav',\n 'audio/ogg': '.ogg',\n 'video/mp4': '.mp4',\n 'video/webm': '.webm',\n 'application/pdf': '.pdf',\n 'text/plain': '.txt',\n 'application/json': '.json',\n };\n \n return extensionMap[contentType] || '';\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/util/logger.ts","../../../src/util/packer/CouchDBToStaticPacker.ts"],"sourcesContent":["/**\n * Simple logging utility for @vue-skuilder/db package\n *\n * This utility provides environment-aware logging with ESLint suppressions\n * to resolve console statement violations while maintaining logging functionality.\n */\n\nconst isDevelopment = typeof process !== 'undefined' && process.env.NODE_ENV === 'development';\nconst _isBrowser = typeof window !== 'undefined';\n\nexport const logger = {\n /**\n * Debug-level logging - only shown in development\n */\n debug: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.debug(`[DB:DEBUG] ${message}`, ...args);\n }\n },\n\n /**\n * Info-level logging - general information\n */\n info: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.info(`[DB:INFO] ${message}`, ...args);\n },\n\n /**\n * Warning-level logging - potential issues\n */\n warn: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.warn(`[DB:WARN] ${message}`, ...args);\n },\n\n /**\n * Error-level logging - serious problems\n */\n error: (message: string, ...args: any[]): void => {\n // eslint-disable-next-line no-console\n console.error(`[DB:ERROR] ${message}`, ...args);\n },\n\n /**\n * Log function for backward compatibility with existing log() usage\n */\n log: (message: string, ...args: any[]): void => {\n if (isDevelopment) {\n // eslint-disable-next-line no-console\n console.log(`[DB:LOG] ${message}`, ...args);\n }\n },\n};\n","// packages/db/src/util/packer/CouchDBToStaticPacker.ts\n\nimport { CardData, DocType, Tag } from '../../core/types/types-legacy';\nimport { logger } from '../logger';\n// CourseConfig interface - simplified for packer use\n\nimport { CourseConfig } from '@vue-skuilder/common';\nimport {\n ChunkMetadata,\n DesignDocument,\n IndexMetadata,\n PackedCourseData,\n PackerConfig,\n StaticCourseManifest,\n AttachmentData,\n} from './types';\nimport { FileSystemAdapter } from '../migrator/FileSystemAdapter';\n\nexport class CouchDBToStaticPacker {\n private config: PackerConfig;\n private sourceDB: PouchDB.Database | null = null;\n\n constructor(config: Partial<PackerConfig> = {}) {\n this.config = {\n chunkSize: 1000,\n includeAttachments: true,\n ...config,\n };\n }\n\n /**\n * Pack a CouchDB course database into static data structures\n */\n async packCourse(sourceDB: PouchDB.Database, courseId: string): Promise<PackedCourseData> {\n logger.info(`Starting static pack for course: ${courseId}`);\n this.sourceDB = sourceDB;\n\n const manifest: StaticCourseManifest = {\n version: '1.0.0',\n courseId,\n courseName: '',\n courseConfig: null,\n lastUpdated: new Date().toISOString(),\n documentCount: 0,\n chunks: [],\n indices: [],\n designDocs: [],\n };\n\n // 1. Extract course config\n const courseConfig = await this.extractCourseConfig(sourceDB);\n manifest.courseName = courseConfig.name;\n manifest.courseConfig = courseConfig;\n\n // 2. Extract and process design documents\n manifest.designDocs = await this.extractDesignDocs(sourceDB);\n\n // 3. Extract all documents by type and create chunks\n const docsByType = await this.extractDocumentsByType(sourceDB);\n\n // 4. Extract attachments if enabled\n const attachments = new Map<string, AttachmentData>();\n if (this.config.includeAttachments) {\n await this.extractAllAttachments(docsByType, attachments);\n }\n\n // 5. Create chunks and prepare chunk data\n const chunks = new Map<string, any[]>();\n for (const [docType, docs] of Object.entries(docsByType)) {\n const chunkMetadata = this.createChunks(docs, docType as DocType);\n manifest.chunks.push(...chunkMetadata);\n manifest.documentCount += docs.length;\n\n // Prepare chunk data\n this.prepareChunkData(chunkMetadata, docs, chunks);\n }\n\n // 6. Build indices\n const indices = new Map<string, any>();\n manifest.indices = await this.buildIndices(docsByType, manifest.designDocs, indices);\n\n return {\n manifest,\n chunks,\n indices,\n attachments,\n };\n }\n\n /**\n * Pack a CouchDB course database and write the static files to disk\n */\n async packCourseToFiles(\n sourceDB: PouchDB.Database, \n courseId: string, \n outputDir: string, \n fsAdapter: FileSystemAdapter\n ): Promise<{ \n manifest: StaticCourseManifest; \n filesWritten: number; \n attachmentsFound: number; \n }> {\n logger.info(`Packing course ${courseId} to files in ${outputDir}`);\n \n // First, pack the course data\n const packedData = await this.packCourse(sourceDB, courseId);\n \n // Write the files using the FileSystemAdapter\n const filesWritten = await this.writePackedDataToFiles(packedData, outputDir, fsAdapter);\n \n return {\n manifest: packedData.manifest,\n filesWritten,\n attachmentsFound: packedData.attachments ? packedData.attachments.size : 0,\n };\n }\n\n /**\n * Write packed course data to files using FileSystemAdapter\n */\n private async writePackedDataToFiles(\n packedData: PackedCourseData,\n outputDir: string,\n fsAdapter: FileSystemAdapter\n ): Promise<number> {\n let totalFiles = 0;\n \n // Ensure output directory exists\n await fsAdapter.ensureDir(outputDir);\n \n // Write manifest\n const manifestPath = fsAdapter.joinPath(outputDir, 'manifest.json');\n await fsAdapter.writeJson(manifestPath, packedData.manifest, { spaces: 2 });\n totalFiles++;\n logger.info(`Wrote manifest: ${manifestPath}`);\n \n // Create subdirectories\n const chunksDir = fsAdapter.joinPath(outputDir, 'chunks');\n const indicesDir = fsAdapter.joinPath(outputDir, 'indices');\n await fsAdapter.ensureDir(chunksDir);\n await fsAdapter.ensureDir(indicesDir);\n \n // Write chunks\n for (const [chunkId, chunkData] of packedData.chunks) {\n const chunkPath = fsAdapter.joinPath(chunksDir, `${chunkId}.json`);\n await fsAdapter.writeJson(chunkPath, chunkData);\n totalFiles++;\n }\n logger.info(`Wrote ${packedData.chunks.size} chunk files`);\n \n // Write indices\n for (const [indexName, indexData] of packedData.indices) {\n const indexPath = fsAdapter.joinPath(indicesDir, `${indexName}.json`);\n await fsAdapter.writeJson(indexPath, indexData, { spaces: 2 });\n totalFiles++;\n }\n logger.info(`Wrote ${packedData.indices.size} index files`);\n \n // Write attachments\n if (packedData.attachments && packedData.attachments.size > 0) {\n for (const [attachmentPath, attachmentData] of packedData.attachments) {\n const fullAttachmentPath = fsAdapter.joinPath(outputDir, attachmentPath);\n \n // Ensure attachment directory exists\n const attachmentDir = fsAdapter.dirname(fullAttachmentPath);\n await fsAdapter.ensureDir(attachmentDir);\n \n // Write binary file\n await fsAdapter.writeFile(fullAttachmentPath, attachmentData.buffer);\n totalFiles++;\n }\n logger.info(`Wrote ${packedData.attachments.size} attachment files`);\n }\n \n return totalFiles;\n }\n\n private async extractCourseConfig(db: PouchDB.Database): Promise<CourseConfig> {\n try {\n return await db.get<CourseConfig>('CourseConfig');\n } catch (error) {\n logger.error('Failed to extract course config:', error);\n throw new Error('Course config not found');\n }\n }\n\n private async extractDesignDocs(db: PouchDB.Database): Promise<DesignDocument[]> {\n const result = await db.allDocs({\n startkey: '_design/',\n endkey: '_design/\\ufff0',\n include_docs: true,\n });\n\n return result.rows.map((row) => ({\n _id: row.id,\n views: (row.doc as any).views || {},\n }));\n }\n\n private async extractDocumentsByType(db: PouchDB.Database): Promise<Record<DocType, any[]>> {\n const allDocs = await db.allDocs({ include_docs: true });\n const docsByType: Record<string, any[]> = {};\n\n for (const row of allDocs.rows) {\n if (row.id.startsWith('_')) continue; // Skip design docs\n\n const doc = row.doc as any;\n if (doc.docType) {\n if (!docsByType[doc.docType]) {\n docsByType[doc.docType] = [];\n }\n docsByType[doc.docType].push(doc);\n }\n }\n\n return docsByType as Record<DocType, any[]>;\n }\n\n private createChunks(docs: any[], docType: DocType): ChunkMetadata[] {\n const chunks: ChunkMetadata[] = [];\n const sortedDocs = docs.sort((a, b) => a._id.localeCompare(b._id));\n\n for (let i = 0; i < sortedDocs.length; i += this.config.chunkSize) {\n const chunk = sortedDocs.slice(i, i + this.config.chunkSize);\n const chunkId = `${docType}-${String(Math.floor(i / this.config.chunkSize)).padStart(4, '0')}`;\n\n chunks.push({\n id: chunkId,\n docType,\n startKey: chunk[0]._id,\n endKey: chunk[chunk.length - 1]._id,\n documentCount: chunk.length,\n path: `chunks/${chunkId}.json`,\n });\n }\n\n return chunks;\n }\n\n private prepareChunkData(\n chunkMetadata: ChunkMetadata[],\n docs: any[],\n chunks: Map<string, any[]>\n ): void {\n const sortedDocs = docs.sort((a, b) => a._id.localeCompare(b._id));\n\n for (const chunk of chunkMetadata) {\n const chunkDocs = sortedDocs.filter(\n (doc) => doc._id >= chunk.startKey && doc._id <= chunk.endKey\n );\n\n // Clean documents for storage\n const cleanedDocs = chunkDocs.map((doc) => {\n const cleaned = { ...doc };\n delete cleaned._rev; // Remove revision info\n \n if (this.config.includeAttachments && cleaned._attachments) {\n // Transform attachment stubs to file paths\n cleaned._attachments = this.transformAttachmentStubs(cleaned._attachments, cleaned._id);\n } else if (!this.config.includeAttachments) {\n delete cleaned._attachments;\n }\n \n return cleaned;\n });\n\n chunks.set(chunk.id, cleanedDocs);\n }\n }\n\n private async buildIndices(\n docsByType: Record<DocType, any[]>,\n designDocs: DesignDocument[],\n indices: Map<string, any>\n ): Promise<IndexMetadata[]> {\n const indexMetadata: IndexMetadata[] = [];\n\n // Build ELO index\n if (docsByType[DocType.CARD]) {\n const eloIndexMeta = await this.buildEloIndex(\n docsByType[DocType.CARD] as CardData[],\n indices\n );\n indexMetadata.push(eloIndexMeta);\n }\n\n // Build tag indices\n if (docsByType[DocType.TAG]) {\n const tagIndexMeta = await this.buildTagIndex(docsByType[DocType.TAG] as Tag[], indices);\n indexMetadata.push(tagIndexMeta);\n }\n\n // Build indices from design documents using CouchDB view queries\n for (const designDoc of designDocs) {\n for (const [viewName, viewDef] of Object.entries(designDoc.views)) {\n if (viewDef.map) {\n logger.info(`Processing view: ${designDoc._id}/${viewName}`);\n const indexMeta = await this.buildViewIndex(viewName, designDoc, indices);\n if (indexMeta) {\n indexMetadata.push(indexMeta);\n logger.info(`Successfully built index: ${indexMeta.name}`);\n } else {\n logger.warn(`Skipped view index: ${designDoc._id}/${viewName}`);\n }\n }\n }\n }\n\n return indexMetadata;\n }\n\n private async buildEloIndex(\n cards: CardData[],\n indices: Map<string, any>\n ): Promise<IndexMetadata> {\n // Build a B-tree like structure for ELO queries\n const eloIndex: Array<{ elo: number; cardId: string }> = [];\n\n for (const card of cards) {\n if (card.elo?.global?.score) {\n eloIndex.push({\n elo: card.elo.global.score,\n cardId: (card as any)._id,\n });\n }\n }\n\n // Sort by ELO for efficient range queries\n eloIndex.sort((a, b) => a.elo - b.elo);\n\n // Create buckets for faster lookup\n const buckets: Record<number, string[]> = {};\n const bucketSize = 50; // ELO points per bucket\n\n for (const entry of eloIndex) {\n const bucket = Math.floor(entry.elo / bucketSize) * bucketSize;\n if (!buckets[bucket]) buckets[bucket] = [];\n buckets[bucket].push(entry.cardId);\n }\n\n // Store the index data\n indices.set('elo', {\n sorted: eloIndex,\n buckets: buckets,\n stats: {\n min: eloIndex[0]?.elo || 0,\n max: eloIndex[eloIndex.length - 1]?.elo || 0,\n count: eloIndex.length,\n },\n });\n\n return {\n name: 'elo',\n type: 'btree',\n path: 'indices/elo.json',\n };\n }\n\n private async buildTagIndex(tags: Tag[], indices: Map<string, any>): Promise<IndexMetadata> {\n // Build inverted index for tags\n const tagIndex: Record<\n string,\n {\n cardIds: string[];\n snippet: string;\n count: number;\n }\n > = {};\n\n for (const tag of tags) {\n tagIndex[tag.name] = {\n cardIds: tag.taggedCards,\n snippet: tag.snippet,\n count: tag.taggedCards.length,\n };\n }\n\n // Also build a reverse index (card -> tags)\n const cardToTags: Record<string, string[]> = {};\n for (const tag of tags) {\n for (const cardId of tag.taggedCards) {\n if (!cardToTags[cardId]) cardToTags[cardId] = [];\n cardToTags[cardId].push(tag.name);\n }\n }\n\n indices.set('tags', {\n byTag: tagIndex,\n byCard: cardToTags,\n });\n\n return {\n name: 'tags',\n type: 'hash',\n path: 'indices/tags.json',\n };\n }\n\n /**\n * Build view index by querying CouchDB views directly instead of parsing map functions\n */\n private async buildViewIndex(\n viewName: string,\n designDoc: DesignDocument,\n indices: Map<string, any>\n ): Promise<IndexMetadata | null> {\n if (!this.sourceDB) {\n logger.error('Source database not available for view querying');\n return null;\n }\n\n try {\n const designDocId = designDoc._id; // e.g., \"_design/elo\"\n const designDocName = designDocId.replace('_design/', ''); // Extract just \"elo\"\n const viewPath = `${designDocName}/${viewName}`;\n \n logger.info(`Querying CouchDB view: ${viewPath}`);\n \n // Query the view directly from CouchDB using PouchDB format: \"designDocName/viewName\"\n const viewResults = await this.sourceDB.query(viewPath, {\n include_docs: false,\n });\n\n if (!viewResults.rows || viewResults.rows.length === 0) {\n logger.warn(`View ${viewPath} returned no results`);\n return null;\n }\n\n logger.info(`Successfully queried view ${viewPath}: ${viewResults.rows.length} results`);\n\n // Format the results for static consumption\n const formattedResults = this.formatViewResults(viewName, viewResults.rows, designDoc);\n \n const indexName = `view-${designDoc._id.replace('_design/', '')}-${viewName}`;\n indices.set(indexName, formattedResults);\n\n return {\n name: indexName,\n type: 'view',\n path: `indices/${indexName}.json`,\n };\n } catch (error) {\n logger.error(`Failed to query view ${designDoc._id}/${viewName}:`, error);\n // Return null to gracefully skip this view rather than failing the entire pack\n return null;\n }\n }\n\n /**\n * Format CouchDB view results for static consumption\n */\n private formatViewResults(\n viewName: string,\n viewRows: Array<{ key: any; value: any; id: string }>,\n designDoc: DesignDocument\n ): any {\n const baseResult = {\n type: 'couchdb-view',\n viewName,\n designDoc: designDoc._id,\n results: viewRows,\n metadata: {\n resultCount: viewRows.length,\n generatedAt: new Date().toISOString(),\n },\n };\n\n // Apply view-specific formatting\n switch (viewName) {\n case 'elo':\n return this.formatEloViewIndex(viewRows, baseResult);\n case 'getTags':\n return this.formatTagsViewIndex(viewRows, baseResult);\n case 'cardsByInexperience':\n return this.formatInexperienceViewIndex(viewRows, baseResult);\n default:\n return this.formatGenericViewIndex(viewRows, baseResult);\n }\n }\n\n /**\n * Format ELO view results - convert to sorted array format\n */\n private formatEloViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Sort by ELO score (key) for efficient range queries\n const sortedResults = viewRows.sort((a, b) => {\n if (typeof a.key === 'number' && typeof b.key === 'number') {\n return a.key - b.key;\n }\n return 0;\n });\n\n return {\n ...baseResult,\n sorted: sortedResults,\n stats: {\n min: sortedResults[0]?.key || 0,\n max: sortedResults[sortedResults.length - 1]?.key || 0,\n count: sortedResults.length,\n },\n };\n }\n\n /**\n * Format tags view results - convert to tag mapping format\n */\n private formatTagsViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Group by tag name (key)\n const tagMap: Record<string, string[]> = {};\n \n for (const row of viewRows) {\n const tagName = row.key;\n if (typeof tagName === 'string') {\n if (!tagMap[tagName]) {\n tagMap[tagName] = [];\n }\n tagMap[tagName].push(row.id);\n }\n }\n\n return {\n ...baseResult,\n byTag: tagMap,\n tagCount: Object.keys(tagMap).length,\n };\n }\n\n /**\n * Format inexperience view results - convert to experience-based format\n */\n private formatInexperienceViewIndex(\n viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n // Sort by inexperience level (key) - lower numbers = less experience\n const sortedResults = viewRows.sort((a, b) => {\n if (typeof a.key === 'number' && typeof b.key === 'number') {\n return a.key - b.key;\n }\n return 0;\n });\n\n return {\n ...baseResult,\n sorted: sortedResults,\n stats: {\n minInexperience: sortedResults[0]?.key || 0,\n maxInexperience: sortedResults[sortedResults.length - 1]?.key || 0,\n count: sortedResults.length,\n },\n };\n }\n\n /**\n * Format generic view results - fallback for unknown views\n */\n private formatGenericViewIndex(\n _viewRows: Array<{ key: any; value: any; id: string }>,\n baseResult: any\n ): any {\n return {\n ...baseResult,\n // Keep results as-is for unknown view types\n };\n }\n\n /**\n * Extract all attachments from documents and download binary data\n */\n private async extractAllAttachments(\n docsByType: Record<DocType, any[]>,\n attachments: Map<string, AttachmentData>\n ): Promise<void> {\n logger.info('Extracting attachments...');\n \n const allDocs: any[] = [];\n for (const docs of Object.values(docsByType)) {\n allDocs.push(...docs);\n }\n\n const docsWithAttachments = allDocs.filter(doc => doc._attachments && Object.keys(doc._attachments).length > 0);\n \n if (docsWithAttachments.length === 0) {\n logger.info('No attachments found');\n return;\n }\n\n logger.info(`Found ${docsWithAttachments.length} documents with attachments`);\n\n // Process attachments concurrently\n const extractionPromises = docsWithAttachments.map(doc => \n this.extractDocumentAttachments(doc, attachments)\n );\n\n await Promise.all(extractionPromises);\n \n logger.info(`Extracted ${attachments.size} attachment files`);\n }\n\n /**\n * Extract attachments for a single document\n */\n private async extractDocumentAttachments(\n doc: any,\n attachments: Map<string, AttachmentData>\n ): Promise<void> {\n if (!doc._attachments || !this.sourceDB) {\n return;\n }\n\n const docId = doc._id;\n \n for (const [attachmentName, metadata] of Object.entries(doc._attachments as Record<string, any>)) {\n try {\n // Download attachment binary data\n const attachmentResponse = await this.sourceDB.getAttachment(docId, attachmentName);\n \n // Convert to buffer\n let buffer: Buffer;\n if (attachmentResponse instanceof ArrayBuffer) {\n buffer = Buffer.from(attachmentResponse);\n } else if (Buffer.isBuffer(attachmentResponse)) {\n buffer = attachmentResponse;\n } else {\n // For browser environments, the response might be a Blob\n const blob = attachmentResponse as Blob;\n buffer = Buffer.from(await blob.arrayBuffer());\n }\n\n // Generate filename with proper extension\n const extension = this.getFileExtension(metadata.content_type);\n const filename = `${attachmentName}${extension}`;\n const attachmentPath = `attachments/${docId}/${filename}`;\n \n // Store attachment data\n attachments.set(attachmentPath, {\n docId,\n attachmentName,\n filename,\n path: attachmentPath,\n contentType: metadata.content_type,\n length: metadata.length || buffer.length,\n digest: metadata.digest,\n buffer,\n });\n\n logger.debug(`Extracted attachment: ${attachmentPath}`);\n } catch (error) {\n logger.error(`Failed to extract attachment ${docId}/${attachmentName}:`, error);\n throw new Error(`Failed to extract attachment ${docId}/${attachmentName}: ${error}`);\n }\n }\n }\n\n /**\n * Transform attachment stubs to include file paths\n */\n private transformAttachmentStubs(\n attachments: Record<string, any>,\n docId: string\n ): Record<string, any> {\n const transformed: Record<string, any> = {};\n \n for (const [attachmentName, metadata] of Object.entries(attachments)) {\n const extension = this.getFileExtension(metadata.content_type);\n const filename = `${attachmentName}${extension}`;\n const attachmentPath = `attachments/${docId}/${filename}`;\n \n transformed[attachmentName] = {\n path: attachmentPath,\n content_type: metadata.content_type,\n length: metadata.length,\n digest: metadata.digest,\n stub: false, // No longer a stub - we have the actual file\n };\n }\n \n return transformed;\n }\n\n /**\n * Get file extension from content type\n */\n private getFileExtension(contentType: string): string {\n const extensionMap: Record<string, string> = {\n 'image/jpeg': '.jpg',\n 'image/jpg': '.jpg', \n 'image/png': '.png',\n 'image/gif': '.gif',\n 'image/webp': '.webp',\n 'audio/mpeg': '.mp3',\n 'audio/mp3': '.mp3',\n 'audio/wav': '.wav',\n 'audio/ogg': '.ogg',\n 'video/mp4': '.mp4',\n 'video/webm': '.webm',\n 'application/pdf': '.pdf',\n 'text/plain': '.txt',\n 'application/json': '.json',\n };\n \n return extensionMap[contentType] || '';\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.8-0",
|
|
7
7
|
"description": "Database layer for vue-skuilder",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"module": "dist/index.mjs",
|
|
@@ -45,13 +45,16 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@nilock2/pouchdb-authentication": "^1.0.2",
|
|
48
|
-
"@vue-skuilder/common": "0.1.
|
|
48
|
+
"@vue-skuilder/common": "0.1.8-0",
|
|
49
49
|
"moment": "^2.29.4",
|
|
50
50
|
"pouchdb": "^9.0.0",
|
|
51
|
-
"pouchdb-find": "^9.0.0"
|
|
51
|
+
"pouchdb-find": "^9.0.0",
|
|
52
|
+
"uuid": "^11.1.0"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
55
|
+
"@types/uuid": "^10.0.0",
|
|
54
56
|
"tsup": "^8.0.2",
|
|
55
57
|
"typescript": "~5.7.2"
|
|
56
|
-
}
|
|
58
|
+
},
|
|
59
|
+
"stableVersion": "0.1.7"
|
|
57
60
|
}
|
|
@@ -86,7 +86,19 @@ export interface QuestionData extends SkuilderCourseData {
|
|
|
86
86
|
dataShapeList: PouchDB.Core.DocumentId[];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
export const
|
|
89
|
+
export const DocTypePrefixes: Record<string, string> = {
|
|
90
|
+
[DocType.CARD]: 'c',
|
|
91
|
+
[DocType.DISPLAYABLE_DATA]: 'dd',
|
|
92
|
+
[DocType.TAG]: 'TAG',
|
|
93
|
+
[DocType.CARDRECORD]: 'cardH',
|
|
94
|
+
[DocType.SCHEDULED_CARD]: 'card_review_',
|
|
95
|
+
// Add other doctypes here as they get prefixed IDs
|
|
96
|
+
[DocType.DATASHAPE]: 'DATASHAPE',
|
|
97
|
+
[DocType.QUESTIONTYPE]: 'QUESTION',
|
|
98
|
+
[DocType.VIEW]: 'VIEW',
|
|
99
|
+
[DocType.PEDAGOGY]: 'PEDAGOGY',
|
|
100
|
+
[DocType.NAVIGATION_STRATEGY]: 'NAVIGATION_STRATEGY',
|
|
101
|
+
};
|
|
90
102
|
|
|
91
103
|
export interface CardHistory<T extends CardRecord> {
|
|
92
104
|
_id: PouchDB.Core.DocumentId;
|
package/src/core/types/user.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Moment } from 'moment';
|
|
|
4
4
|
export interface UserConfig {
|
|
5
5
|
darkMode: boolean;
|
|
6
6
|
likesConfetti: boolean;
|
|
7
|
+
sessionTimeLimit: number; // Session time limit in minutes
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export interface ActivityRecord {
|
|
@@ -48,13 +49,19 @@ export interface ScheduledCard {
|
|
|
48
49
|
*
|
|
49
50
|
* (Should probably be UTC adjusted so that performance is
|
|
50
51
|
* not wonky across time zones)
|
|
52
|
+
*
|
|
53
|
+
* Note: Stored as ISO string for PouchDB serialization compatibility,
|
|
54
|
+
* but can be consumed as Moment objects via moment.utc(reviewTime)
|
|
51
55
|
*/
|
|
52
|
-
reviewTime: Moment;
|
|
56
|
+
reviewTime: string | Moment;
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
59
|
* The time at which this scheduled event was created.
|
|
60
|
+
*
|
|
61
|
+
* Note: Stored as ISO string for PouchDB serialization compatibility,
|
|
62
|
+
* but can be consumed as Moment objects via moment.utc(scheduledAt)
|
|
56
63
|
*/
|
|
57
|
-
scheduledAt: Moment;
|
|
64
|
+
scheduledAt: string | Moment;
|
|
58
65
|
|
|
59
66
|
/**
|
|
60
67
|
* Classifying whether this card is scheduled on behalf of a
|
package/src/core/util/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DocType, DocTypePrefixes, CardHistory, CardRecord, QuestionRecord } from '../types/types-legacy';
|
|
2
2
|
|
|
3
3
|
export function areQuestionRecords(h: CardHistory<CardRecord>): h is CardHistory<QuestionRecord> {
|
|
4
4
|
return isQuestionRecord(h.records[0]);
|
|
@@ -9,7 +9,7 @@ export function isQuestionRecord(c: CardRecord): c is QuestionRecord {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function getCardHistoryID(courseID: string, cardID: string): PouchDB.Core.DocumentId {
|
|
12
|
-
return `${
|
|
12
|
+
return `${DocTypePrefixes[DocType.CARDRECORD]}-${courseID}-${cardID}`;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function parseCardHistoryID(id: string): {
|
|
@@ -20,9 +20,10 @@ export function parseCardHistoryID(id: string): {
|
|
|
20
20
|
let error: string = '';
|
|
21
21
|
error += split.length === 3 ? '' : `\n\tgiven ID has incorrect number of '-' characters`;
|
|
22
22
|
error +=
|
|
23
|
-
split[0] ===
|
|
23
|
+
split[0] === DocTypePrefixes[DocType.CARDRECORD] ? '' : `
|
|
24
|
+
given ID does not start with ${DocTypePrefixes[DocType.CARDRECORD]}`;
|
|
24
25
|
|
|
25
|
-
if (split.length === 3 && split[0] ===
|
|
26
|
+
if (split.length === 3 && split[0] === DocTypePrefixes[DocType.CARDRECORD]) {
|
|
26
27
|
return {
|
|
27
28
|
courseID: split[1],
|
|
28
29
|
cardID: split[2],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DocType, DocTypePrefixes } from '@db/core';
|
|
1
2
|
import { getCardHistoryID } from '@db/core/util';
|
|
2
3
|
import { CourseElo, Status } from '@vue-skuilder/common';
|
|
3
4
|
import moment, { Moment } from 'moment';
|
|
@@ -23,7 +24,6 @@ import type { SyncStrategy } from './SyncStrategy';
|
|
|
23
24
|
import {
|
|
24
25
|
filterAllDocsByPrefix,
|
|
25
26
|
getStartAndEndKeys,
|
|
26
|
-
REVIEW_PREFIX,
|
|
27
27
|
REVIEW_TIME_FORMAT,
|
|
28
28
|
getLocalUserDB,
|
|
29
29
|
scheduleCardReviewLocal,
|
|
@@ -38,8 +38,6 @@ const log = (s: any) => {
|
|
|
38
38
|
logger.info(s);
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
const cardHistoryPrefix = 'cardH-';
|
|
42
|
-
|
|
43
41
|
// console.log(`Connecting to remote: ${remoteStr}`);
|
|
44
42
|
|
|
45
43
|
interface DesignDoc {
|
|
@@ -81,11 +79,14 @@ export class BaseUser implements UserDBInterface, DocumentUpdater {
|
|
|
81
79
|
return !this._username.startsWith(GuestUsername);
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
private remoteDB!: PouchDB.Database;
|
|
85
82
|
public remote(): PouchDB.Database {
|
|
86
83
|
return this.remoteDB;
|
|
87
84
|
}
|
|
85
|
+
|
|
88
86
|
private localDB!: PouchDB.Database;
|
|
87
|
+
private remoteDB!: PouchDB.Database;
|
|
88
|
+
private writeDB!: PouchDB.Database; // Database to use for write operations (local-first approach)
|
|
89
|
+
|
|
89
90
|
private updateQueue!: UpdateQueue;
|
|
90
91
|
|
|
91
92
|
public async createAccount(
|
|
@@ -174,8 +175,8 @@ Currently logged-in as ${this._username}.`
|
|
|
174
175
|
const id = row.id;
|
|
175
176
|
// Delete user progress data but preserve core user documents
|
|
176
177
|
return (
|
|
177
|
-
id.startsWith(
|
|
178
|
-
id.startsWith(
|
|
178
|
+
id.startsWith(DocTypePrefixes[DocType.CARDRECORD]) || // Card interaction history
|
|
179
|
+
id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD]) || // Scheduled reviews
|
|
179
180
|
id === BaseUser.DOC_IDS.COURSE_REGISTRATIONS || // Course registrations
|
|
180
181
|
id === BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS || // Classroom registrations
|
|
181
182
|
id === BaseUser.DOC_IDS.CONFIG // User config
|
|
@@ -264,7 +265,7 @@ Currently logged-in as ${this._username}.`
|
|
|
264
265
|
*
|
|
265
266
|
*/
|
|
266
267
|
public async getActiveCards() {
|
|
267
|
-
const keys = getStartAndEndKeys(
|
|
268
|
+
const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);
|
|
268
269
|
|
|
269
270
|
const reviews = await this.remoteDB.allDocs<ScheduledCard>({
|
|
270
271
|
startkey: keys.startkey,
|
|
@@ -359,7 +360,7 @@ Currently logged-in as ${this._username}.`
|
|
|
359
360
|
}
|
|
360
361
|
|
|
361
362
|
private async getReviewstoDate(targetDate: Moment, course_id?: string) {
|
|
362
|
-
const keys = getStartAndEndKeys(
|
|
363
|
+
const keys = getStartAndEndKeys(DocTypePrefixes[DocType.SCHEDULED_CARD]);
|
|
363
364
|
|
|
364
365
|
const reviews = await this.remoteDB.allDocs<ScheduledCard>({
|
|
365
366
|
startkey: keys.startkey,
|
|
@@ -374,8 +375,11 @@ Currently logged-in as ${this._username}.`
|
|
|
374
375
|
);
|
|
375
376
|
return reviews.rows
|
|
376
377
|
.filter((r) => {
|
|
377
|
-
if (r.id.startsWith(
|
|
378
|
-
const date = moment.utc(
|
|
378
|
+
if (r.id.startsWith(DocTypePrefixes[DocType.SCHEDULED_CARD])) {
|
|
379
|
+
const date = moment.utc(
|
|
380
|
+
r.id.substr(DocTypePrefixes[DocType.SCHEDULED_CARD].length),
|
|
381
|
+
REVIEW_TIME_FORMAT
|
|
382
|
+
);
|
|
379
383
|
if (targetDate.isAfter(date)) {
|
|
380
384
|
if (course_id === undefined || r.doc!.courseId === course_id) {
|
|
381
385
|
return true;
|
|
@@ -515,6 +519,7 @@ Currently logged-in as ${this._username}.`
|
|
|
515
519
|
_id: BaseUser.DOC_IDS.CONFIG,
|
|
516
520
|
darkMode: false,
|
|
517
521
|
likesConfetti: false,
|
|
522
|
+
sessionTimeLimit: 5,
|
|
518
523
|
};
|
|
519
524
|
|
|
520
525
|
try {
|
|
@@ -596,7 +601,11 @@ Currently logged-in as ${this._username}.`
|
|
|
596
601
|
private setDBandQ() {
|
|
597
602
|
this.localDB = getLocalUserDB(this._username);
|
|
598
603
|
this.remoteDB = this.syncStrategy.setupRemoteDB(this._username);
|
|
599
|
-
|
|
604
|
+
// writeDB follows local-first pattern: static mode writes to local, CouchDB writes to remote/local as appropriate
|
|
605
|
+
this.writeDB = this.syncStrategy.getWriteDB
|
|
606
|
+
? this.syncStrategy.getWriteDB(this._username)
|
|
607
|
+
: this.localDB;
|
|
608
|
+
this.updateQueue = new UpdateQueue(this.localDB, this.writeDB);
|
|
600
609
|
}
|
|
601
610
|
|
|
602
611
|
private async init() {
|
|
@@ -696,7 +705,9 @@ Currently logged-in as ${this._username}.`
|
|
|
696
705
|
* @returns The updated state of the card's CardHistory data
|
|
697
706
|
*/
|
|
698
707
|
|
|
699
|
-
public async putCardRecord<T extends CardRecord>(
|
|
708
|
+
public async putCardRecord<T extends CardRecord>(
|
|
709
|
+
record: T
|
|
710
|
+
): Promise<CardHistory<CardRecord> & PouchDB.Core.RevisionIdMeta> {
|
|
700
711
|
const cardHistoryID = getCardHistoryID(record.courseID, record.cardID);
|
|
701
712
|
// stringify the current record to make it writable to couchdb
|
|
702
713
|
record.timeStamp = moment.utc(record.timeStamp).toString() as unknown as Moment;
|
|
@@ -734,8 +745,8 @@ Currently logged-in as ${this._username}.`
|
|
|
734
745
|
streak: 0,
|
|
735
746
|
bestInterval: 0,
|
|
736
747
|
};
|
|
737
|
-
|
|
738
|
-
return initCardHistory;
|
|
748
|
+
const putResult = await this.writeDB.put<CardHistory<T>>(initCardHistory);
|
|
749
|
+
return { ...initCardHistory, _rev: putResult.rev };
|
|
739
750
|
} else {
|
|
740
751
|
throw new Error(`putCardRecord failed because of:
|
|
741
752
|
name:${reason.name}
|
|
@@ -792,7 +803,7 @@ Currently logged-in as ${this._username}.`
|
|
|
792
803
|
const deletePromises = duplicateDocIds.map(async (docId) => {
|
|
793
804
|
try {
|
|
794
805
|
const doc = await this.remoteDB.get(docId);
|
|
795
|
-
await this.
|
|
806
|
+
await this.writeDB.remove(doc);
|
|
796
807
|
log(`Successfully removed duplicate review: ${docId}`);
|
|
797
808
|
} catch (error) {
|
|
798
809
|
log(`Failed to remove duplicate review ${docId}: ${error}`);
|
|
@@ -816,7 +827,7 @@ Currently logged-in as ${this._username}.`
|
|
|
816
827
|
* @param course_id optional specification of individual course
|
|
817
828
|
*/
|
|
818
829
|
async getSeenCards(course_id?: string) {
|
|
819
|
-
let prefix =
|
|
830
|
+
let prefix = DocTypePrefixes[DocType.CARDRECORD];
|
|
820
831
|
if (course_id) {
|
|
821
832
|
prefix += course_id;
|
|
822
833
|
}
|
|
@@ -826,8 +837,8 @@ Currently logged-in as ${this._username}.`
|
|
|
826
837
|
// const docs = await this.localDB.allDocs({});
|
|
827
838
|
const ret: PouchDB.Core.DocumentId[] = [];
|
|
828
839
|
docs.rows.forEach((row) => {
|
|
829
|
-
if (row.id.startsWith(
|
|
830
|
-
ret.push(row.id.substr(
|
|
840
|
+
if (row.id.startsWith(DocTypePrefixes[DocType.CARDRECORD])) {
|
|
841
|
+
ret.push(row.id.substr(DocTypePrefixes[DocType.CARDRECORD].length));
|
|
831
842
|
}
|
|
832
843
|
});
|
|
833
844
|
return ret;
|
|
@@ -840,7 +851,7 @@ Currently logged-in as ${this._username}.`
|
|
|
840
851
|
async getHistory() {
|
|
841
852
|
const cards = await filterAllDocsByPrefix<CardHistory<CardRecord>>(
|
|
842
853
|
this.remoteDB,
|
|
843
|
-
|
|
854
|
+
DocTypePrefixes[DocType.CARDRECORD],
|
|
844
855
|
{
|
|
845
856
|
include_docs: true,
|
|
846
857
|
attachments: false,
|
|
@@ -890,7 +901,7 @@ Currently logged-in as ${this._username}.`
|
|
|
890
901
|
|
|
891
902
|
if (err.status === 404) {
|
|
892
903
|
// doc does not exist. Create it and then run this fcn again.
|
|
893
|
-
await this.
|
|
904
|
+
await this.writeDB.put<ClassroomRegistrationDoc>({
|
|
894
905
|
_id: BaseUser.DOC_IDS.CLASSROOM_REGISTRATIONS,
|
|
895
906
|
registrations: [],
|
|
896
907
|
});
|
|
@@ -954,10 +965,10 @@ Currently logged-in as ${this._username}.`
|
|
|
954
965
|
scheduledFor: ScheduledCard['scheduledFor'];
|
|
955
966
|
schedulingAgentId: ScheduledCard['schedulingAgentId'];
|
|
956
967
|
}) {
|
|
957
|
-
return scheduleCardReviewLocal(this.
|
|
968
|
+
return scheduleCardReviewLocal(this.writeDB, review);
|
|
958
969
|
}
|
|
959
970
|
public async removeScheduledCardReview(reviewId: string): Promise<void> {
|
|
960
|
-
return removeScheduledCardReviewLocal(this.
|
|
971
|
+
return removeScheduledCardReviewLocal(this.writeDB, reviewId);
|
|
961
972
|
}
|
|
962
973
|
|
|
963
974
|
public async registerForClassroom(
|
|
@@ -14,6 +14,13 @@ export interface SyncStrategy {
|
|
|
14
14
|
*/
|
|
15
15
|
setupRemoteDB(username: string): PouchDB.Database;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Get the database to use for write operations (local-first approach)
|
|
19
|
+
* @param username The username to get write DB for
|
|
20
|
+
* @returns PouchDB database instance for write operations
|
|
21
|
+
*/
|
|
22
|
+
getWriteDB?(username: string): PouchDB.Database;
|
|
23
|
+
|
|
17
24
|
/**
|
|
18
25
|
* Start synchronization between local and remote databases
|
|
19
26
|
* @param localDB The local PouchDB instance
|
package/src/impl/common/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// packages/db/src/impl/common/userDBHelpers.ts
|
|
2
2
|
|
|
3
3
|
import moment from 'moment';
|
|
4
|
+
import { DocType, DocTypePrefixes } from '@db/core';
|
|
4
5
|
import { logger } from '../../util/logger';
|
|
5
6
|
import { ScheduledCard } from '@db/core/types/user';
|
|
6
7
|
|
|
7
|
-
export const REVIEW_PREFIX: string = 'card_review_';
|
|
8
8
|
export const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';
|
|
9
9
|
|
|
10
10
|
import pouch from '../couch/pouchdb-setup';
|
|
@@ -123,11 +123,11 @@ export function scheduleCardReviewLocal(
|
|
|
123
123
|
const now = moment.utc();
|
|
124
124
|
logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);
|
|
125
125
|
void userDB.put<ScheduledCard>({
|
|
126
|
-
_id:
|
|
126
|
+
_id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),
|
|
127
127
|
cardId: review.card_id,
|
|
128
|
-
reviewTime: review.time,
|
|
128
|
+
reviewTime: review.time.toISOString(),
|
|
129
129
|
courseId: review.course_id,
|
|
130
|
-
scheduledAt: now,
|
|
130
|
+
scheduledAt: now.toISOString(),
|
|
131
131
|
scheduledFor: review.scheduledFor,
|
|
132
132
|
schedulingAgentId: review.schedulingAgentId,
|
|
133
133
|
});
|
|
@@ -32,6 +32,16 @@ export class CouchDBSyncStrategy implements SyncStrategy {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
getWriteDB(username: string): PouchDB.Database {
|
|
36
|
+
if (username === GuestUsername || username.startsWith(GuestUsername)) {
|
|
37
|
+
// Guest users write to local database
|
|
38
|
+
return getLocalUserDB(username);
|
|
39
|
+
} else {
|
|
40
|
+
// Authenticated users write to remote (which will sync to local)
|
|
41
|
+
return this.getUserDB(username);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
startSync(localDB: PouchDB.Database, remoteDB: PouchDB.Database): void {
|
|
36
46
|
// Only sync if local and remote are different instances
|
|
37
47
|
if (localDB !== remoteDB) {
|
|
@@ -6,10 +6,11 @@ import { NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';
|
|
|
6
6
|
import { CourseConfig, DataShape } from '@vue-skuilder/common';
|
|
7
7
|
import { CourseElo, blankCourseElo, toCourseElo } from '@vue-skuilder/common';
|
|
8
8
|
import { CourseDB, createTag } from './courseDB';
|
|
9
|
-
import { CardData, DisplayableData, DocType, Tag } from '../../core/types/types-legacy';
|
|
9
|
+
import { CardData, DisplayableData, DocType, Tag, DocTypePrefixes } from '../../core/types/types-legacy';
|
|
10
10
|
import { prepareNote55 } from '@vue-skuilder/common';
|
|
11
11
|
import { BaseUser } from '../common';
|
|
12
12
|
import { logger } from '@db/util/logger';
|
|
13
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
*
|
|
@@ -33,9 +34,8 @@ export async function addNote55(
|
|
|
33
34
|
): Promise<PouchDB.Core.Response> {
|
|
34
35
|
const db = getCourseDB(courseID);
|
|
35
36
|
const payload = prepareNote55(courseID, codeCourse, shape, data, author, tags, uploads);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const result = await db.post<DisplayableData>(payload);
|
|
37
|
+
const _id = `${DocTypePrefixes[DocType.DISPLAYABLE_DATA]}-${uuidv4()}`;
|
|
38
|
+
const result = await db.put<DisplayableData>({ ...payload, _id });
|
|
39
39
|
|
|
40
40
|
const dataShapeId = NameSpacer.getDataShapeString({
|
|
41
41
|
course: codeCourse,
|
|
@@ -153,9 +153,10 @@ async function addCard(
|
|
|
153
153
|
tags: string[],
|
|
154
154
|
author: string
|
|
155
155
|
): Promise<PouchDB.Core.Response> {
|
|
156
|
-
// [ ] NAMESPACING: consider put( _id: "card-uuid")
|
|
157
156
|
const db = getCourseDB(courseID);
|
|
158
|
-
const
|
|
157
|
+
const _id = `${DocTypePrefixes[DocType.CARD]}-${uuidv4()}`;
|
|
158
|
+
const card = await db.put<CardData>({
|
|
159
|
+
_id,
|
|
159
160
|
course,
|
|
160
161
|
id_displayable_data,
|
|
161
162
|
id_view,
|
package/src/impl/couch/index.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { ENV } from '@db/factory';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DocType,
|
|
4
|
+
DocTypePrefixes,
|
|
5
|
+
GuestUsername,
|
|
6
|
+
log,
|
|
7
|
+
SkuilderCourseData,
|
|
8
|
+
} from '../../core/types/types-legacy';
|
|
3
9
|
// import { getCurrentUser } from '../../stores/useAuthStore';
|
|
4
10
|
import moment, { Moment } from 'moment';
|
|
5
11
|
import { logger } from '@db/util/logger';
|
|
@@ -155,7 +161,6 @@ export async function getRandomCards(courseIDs: string[]) {
|
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
|
|
158
|
-
export const REVIEW_PREFIX: string = 'card_review_';
|
|
159
164
|
export const REVIEW_TIME_FORMAT: string = 'YYYY-MM-DD--kk:mm:ss-SSS';
|
|
160
165
|
|
|
161
166
|
export function getCouchUserDB(username: string): PouchDB.Database {
|
|
@@ -191,11 +196,11 @@ export function scheduleCardReview(review: {
|
|
|
191
196
|
const now = moment.utc();
|
|
192
197
|
logger.info(`Scheduling for review in: ${review.time.diff(now, 'h') / 24} days`);
|
|
193
198
|
void getCouchUserDB(review.user).put<ScheduledCard>({
|
|
194
|
-
_id:
|
|
199
|
+
_id: DocTypePrefixes[DocType.SCHEDULED_CARD] + review.time.format(REVIEW_TIME_FORMAT),
|
|
195
200
|
cardId: review.card_id,
|
|
196
|
-
reviewTime: review.time,
|
|
201
|
+
reviewTime: review.time.toISOString(),
|
|
197
202
|
courseId: review.course_id,
|
|
198
|
-
scheduledAt: now,
|
|
203
|
+
scheduledAt: now.toISOString(),
|
|
199
204
|
scheduledFor: review.scheduledFor,
|
|
200
205
|
schedulingAgentId: review.schedulingAgentId,
|
|
201
206
|
});
|