@vue-skuilder/db 0.1.6 → 0.1.7
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/core/index.d.mts +5 -5
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +717 -667
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +702 -653
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-BuntXkCs.d.ts → dataLayerProvider-6stCgDME.d.ts} +1 -1
- package/dist/{dataLayerProvider-BZmLyBVw.d.mts → dataLayerProvider-BbW9EnZK.d.mts} +1 -1
- package/dist/impl/couch/index.d.mts +3 -3
- package/dist/impl/couch/index.d.ts +3 -3
- package/dist/impl/couch/index.js +1940 -1873
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +1894 -1828
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.mts +4 -4
- package/dist/impl/static/index.d.ts +4 -4
- package/dist/impl/static/index.js +557 -507
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +575 -526
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.mts +244 -8
- package/dist/index.d.ts +244 -8
- package/dist/index.js +3922 -2792
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3897 -2781
- package/dist/index.mjs.map +1 -1
- package/dist/{types-D6SnlHPm.d.ts → types-BvzcRAys.d.ts} +1 -1
- package/dist/{types-DPRvCrIk.d.mts → types-CQQ80R5N.d.mts} +1 -1
- package/dist/{types-legacy-WPe8CtO-.d.mts → types-legacy-CtrmkOLu.d.mts} +1 -1
- package/dist/{types-legacy-WPe8CtO-.d.ts → types-legacy-CtrmkOLu.d.ts} +1 -1
- package/dist/{userDB-31gsvxyd.d.mts → userDB-7fM4tpgr.d.mts} +2 -2
- package/dist/{userDB-D9EuWTp1.d.ts → userDB-DUY63VMN.d.ts} +2 -2
- package/dist/util/packer/index.d.mts +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/package.json +2 -2
- package/src/factory.ts +25 -0
- package/src/impl/common/BaseUserDB.ts +29 -6
- package/src/impl/common/userDBHelpers.ts +11 -1
- package/src/impl/couch/courseLookupDB.ts +24 -0
- package/src/util/dataDirectory.test.ts +53 -0
- package/src/util/dataDirectory.ts +52 -0
- package/src/util/index.ts +3 -0
- package/src/util/migrator/FileSystemAdapter.ts +59 -0
- package/src/util/migrator/StaticToCouchDBMigrator.ts +707 -0
- package/src/util/migrator/index.ts +18 -0
- package/src/util/migrator/types.ts +84 -0
- package/src/util/migrator/validation.ts +517 -0
- package/src/util/tuiLogger.ts +139 -0
|
@@ -136,4 +136,4 @@ interface QuestionRecord extends CardRecord, Evaluation {
|
|
|
136
136
|
priorAttemps: number;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
export { type CardRecord as C, DocType as D, type Field as F, GuestUsername as G, type QuestionData as Q, type SkuilderCourseData as S, type
|
|
139
|
+
export { type CardRecord as C, DocType as D, type Field as F, GuestUsername as G, type QuestionData as Q, type SkuilderCourseData as S, type TagStub as T, type Tag as a, type CardData as b, type CourseListData as c, type DisplayableData as d, type DataShapeData as e, cardHistoryPrefix as f, type CardHistory as g, type QuestionRecord as h, log as l };
|
|
@@ -136,4 +136,4 @@ interface QuestionRecord extends CardRecord, Evaluation {
|
|
|
136
136
|
priorAttemps: number;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
export { type CardRecord as C, DocType as D, type Field as F, GuestUsername as G, type QuestionData as Q, type SkuilderCourseData as S, type
|
|
139
|
+
export { type CardRecord as C, DocType as D, type Field as F, GuestUsername as G, type QuestionData as Q, type SkuilderCourseData as S, type TagStub as T, type Tag as a, type CardData as b, type CourseListData as c, type DisplayableData as d, type DataShapeData as e, cardHistoryPrefix as f, type CardHistory as g, type QuestionRecord as h, log as l };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CourseConfig, ClassroomConfig, CourseElo, Status, SkuilderCourseData as SkuilderCourseData$1, DataShape } from '@vue-skuilder/common';
|
|
2
2
|
import { Moment } from 'moment';
|
|
3
|
-
import { S as SkuilderCourseData, D as DocType,
|
|
3
|
+
import { S as SkuilderCourseData, D as DocType, T as TagStub, a as Tag, g as CardHistory, C as CardRecord } from './types-legacy-CtrmkOLu.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Admin functionality
|
|
@@ -490,4 +490,4 @@ interface ClassroomRegistrationDoc {
|
|
|
490
490
|
registrations: ClassroomRegistration[];
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
-
export { type AdminDBInterface as A, type
|
|
493
|
+
export { type AdminDBInterface as A, type ActivityRecord as B, type CourseDBInterface as C, type DataLayerResult as D, type CourseRegistration as E, type CourseRegistrationDoc as F, type DocumentUpdater as G, newInterval as H, type StudySessionNewItem as S, type TeacherClassroomDBInterface as T, type UserDBInterface as U, type CoursesDBInterface as a, type ClassroomDBInterface as b, type CourseInfo as c, type ContentNavigationStrategyData as d, type StudySessionReviewItem as e, type ScheduledCard as f, type AssignedContent as g, type StudyContentSource as h, type StudentClassroomDBInterface as i, type StudySessionItem as j, type StudySessionFailedItem as k, type StudySessionFailedNewItem as l, type StudySessionFailedReviewItem as m, isReview as n, type ContentSourceID as o, getStudySource as p, type AssignedTag as q, type AssignedCourse as r, type AssignedCard as s, type UserCourseSettings as t, type UserCourseSetting as u, type UsrCrsDataInterface as v, type ClassroomRegistrationDesignation as w, type ClassroomRegistration as x, type ClassroomRegistrationDoc as y, type UserConfig as z };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CourseConfig, ClassroomConfig, CourseElo, Status, SkuilderCourseData as SkuilderCourseData$1, DataShape } from '@vue-skuilder/common';
|
|
2
2
|
import { Moment } from 'moment';
|
|
3
|
-
import { S as SkuilderCourseData, D as DocType,
|
|
3
|
+
import { S as SkuilderCourseData, D as DocType, T as TagStub, a as Tag, g as CardHistory, C as CardRecord } from './types-legacy-CtrmkOLu.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Admin functionality
|
|
@@ -490,4 +490,4 @@ interface ClassroomRegistrationDoc {
|
|
|
490
490
|
registrations: ClassroomRegistration[];
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
-
export { type AdminDBInterface as A, type
|
|
493
|
+
export { type AdminDBInterface as A, type ActivityRecord as B, type CourseDBInterface as C, type DataLayerResult as D, type CourseRegistration as E, type CourseRegistrationDoc as F, type DocumentUpdater as G, newInterval as H, type StudySessionNewItem as S, type TeacherClassroomDBInterface as T, type UserDBInterface as U, type CoursesDBInterface as a, type ClassroomDBInterface as b, type CourseInfo as c, type ContentNavigationStrategyData as d, type StudySessionReviewItem as e, type ScheduledCard as f, type AssignedContent as g, type StudyContentSource as h, type StudentClassroomDBInterface as i, type StudySessionItem as j, type StudySessionFailedItem as k, type StudySessionFailedNewItem as l, type StudySessionFailedReviewItem as m, isReview as n, type ContentSourceID as o, getStudySource as p, type AssignedTag as q, type AssignedCourse as r, type AssignedCard as s, type UserCourseSettings as t, type UserCourseSetting as u, type UsrCrsDataInterface as v, type ClassroomRegistrationDesignation as w, type ClassroomRegistration as x, type ClassroomRegistrationDoc as y, type UserConfig as z };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { P as PackerConfig, a as PackedCourseData } from '../../types-
|
|
2
|
-
export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, S as StaticCourseManifest } from '../../types-
|
|
1
|
+
import { P as PackerConfig, a as PackedCourseData } from '../../types-CQQ80R5N.mjs';
|
|
2
|
+
export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, S as StaticCourseManifest } from '../../types-CQQ80R5N.mjs';
|
|
3
3
|
import '@vue-skuilder/common';
|
|
4
|
-
import '../../types-legacy-
|
|
4
|
+
import '../../types-legacy-CtrmkOLu.mjs';
|
|
5
5
|
import 'moment';
|
|
6
6
|
|
|
7
7
|
declare class CouchDBToStaticPacker {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { P as PackerConfig, a as PackedCourseData } from '../../types-
|
|
2
|
-
export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, S as StaticCourseManifest } from '../../types-
|
|
1
|
+
import { P as PackerConfig, a as PackedCourseData } from '../../types-BvzcRAys.js';
|
|
2
|
+
export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, S as StaticCourseManifest } from '../../types-BvzcRAys.js';
|
|
3
3
|
import '@vue-skuilder/common';
|
|
4
|
-
import '../../types-legacy-
|
|
4
|
+
import '../../types-legacy-CtrmkOLu.js';
|
|
5
5
|
import 'moment';
|
|
6
6
|
|
|
7
7
|
declare class CouchDBToStaticPacker {
|
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.7",
|
|
7
7
|
"description": "Database layer for vue-skuilder",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"module": "dist/index.mjs",
|
|
@@ -45,7 +45,7 @@
|
|
|
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.7",
|
|
49
49
|
"moment": "^2.29.4",
|
|
50
50
|
"pouchdb": "^9.0.0",
|
|
51
51
|
"pouchdb-find": "^9.0.0"
|
package/src/factory.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// db/src/factory.ts
|
|
2
2
|
|
|
3
3
|
import { DataLayerProvider } from './core/interfaces';
|
|
4
|
+
import { BaseUser } from './impl/common';
|
|
4
5
|
import { logger } from './util/logger';
|
|
5
6
|
import { StaticCourseManifest } from './util/packer/types';
|
|
6
7
|
|
|
@@ -53,6 +54,30 @@ export async function initializeDataLayer(config: DataLayerConfig): Promise<Data
|
|
|
53
54
|
ENV.COUCHDB_USERNAME = config.options.COUCHDB_USERNAME;
|
|
54
55
|
ENV.COUCHDB_PASSWORD = config.options.COUCHDB_PASSWORD;
|
|
55
56
|
|
|
57
|
+
if (
|
|
58
|
+
config.options.COUCHDB_PASSWORD &&
|
|
59
|
+
config.options.COUCHDB_USERNAME &&
|
|
60
|
+
typeof window !== 'undefined'
|
|
61
|
+
) {
|
|
62
|
+
// Dynamic import to avoid loading both implementations when only one is needed
|
|
63
|
+
const { CouchDBSyncStrategy } = await import('./impl/couch/CouchDBSyncStrategy');
|
|
64
|
+
|
|
65
|
+
// Create a sync strategy instance and authenticate
|
|
66
|
+
const syncStrategy = new CouchDBSyncStrategy();
|
|
67
|
+
|
|
68
|
+
const user = await BaseUser.instance(syncStrategy, config.options.COUCHDB_USERNAME);
|
|
69
|
+
const authResult = await user.login(
|
|
70
|
+
config.options.COUCHDB_USERNAME,
|
|
71
|
+
config.options.COUCHDB_PASSWORD
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (authResult.ok) {
|
|
75
|
+
logger.info(`Successfully authenticated as ${config.options.COUCHDB_USERNAME}`);
|
|
76
|
+
} else {
|
|
77
|
+
logger.warn(`Authentication failed: ${authResult.error}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
56
81
|
// Dynamic import to avoid loading both implementations when only one is needed
|
|
57
82
|
const { CouchDataLayerProvider } = await import('./impl/couch/PouchDataLayerProvider');
|
|
58
83
|
dataLayerInstance = new CouchDataLayerProvider(config.options.COURSE_IDS);
|
|
@@ -112,7 +112,11 @@ Currently logged-in as ${this._username}.`
|
|
|
112
112
|
if (result.status === Status.ok) {
|
|
113
113
|
log(`Account created successfully, updating username to ${username}`);
|
|
114
114
|
this._username = username;
|
|
115
|
-
|
|
115
|
+
try {
|
|
116
|
+
localStorage.removeItem('dbUUID');
|
|
117
|
+
} catch (e) {
|
|
118
|
+
logger.warn('localStorage not available (Node.js environment):', e);
|
|
119
|
+
}
|
|
116
120
|
await this.init();
|
|
117
121
|
}
|
|
118
122
|
|
|
@@ -126,16 +130,23 @@ Currently logged-in as ${this._username}.`
|
|
|
126
130
|
throw new Error('Authentication not supported by current sync strategy');
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
if (!this._username.startsWith(GuestUsername)) {
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
if (!this._username.startsWith(GuestUsername) && this._username != username) {
|
|
134
|
+
if (this._username != username) {
|
|
135
|
+
throw new Error(`Cannot change accounts while logged in.
|
|
136
|
+
Log out of account ${this.getUsername()} before logging in as ${username}.`);
|
|
137
|
+
}
|
|
138
|
+
logger.warn(`User ${this._username} is already logged in, but executing login again.`);
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
const loginResult = await this.syncStrategy.authenticate!(username, password);
|
|
135
142
|
if (loginResult.ok) {
|
|
136
143
|
log(`Logged in as ${username}`);
|
|
137
144
|
this._username = username;
|
|
138
|
-
|
|
145
|
+
try {
|
|
146
|
+
localStorage.removeItem('dbUUID');
|
|
147
|
+
} catch (e) {
|
|
148
|
+
logger.warn('localStorage not available (Node.js environment):', e);
|
|
149
|
+
}
|
|
139
150
|
await this.init();
|
|
140
151
|
}
|
|
141
152
|
return loginResult;
|
|
@@ -590,6 +601,13 @@ Currently logged-in as ${this._username}.`
|
|
|
590
601
|
|
|
591
602
|
private async init() {
|
|
592
603
|
BaseUser._initialized = false;
|
|
604
|
+
|
|
605
|
+
// Skip admin user
|
|
606
|
+
if (this._username === 'admin') {
|
|
607
|
+
BaseUser._initialized = true;
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
593
611
|
this.setDBandQ();
|
|
594
612
|
|
|
595
613
|
this.syncStrategy.startSync(this.localDB, this.remoteDB);
|
|
@@ -614,6 +632,11 @@ Currently logged-in as ${this._username}.`
|
|
|
614
632
|
];
|
|
615
633
|
|
|
616
634
|
private async applyDesignDocs() {
|
|
635
|
+
if (this._username === 'admin') {
|
|
636
|
+
// Skip admin user
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
617
640
|
for (const doc of BaseUser.designDocs) {
|
|
618
641
|
try {
|
|
619
642
|
// Try to get existing doc
|
|
@@ -633,7 +656,7 @@ Currently logged-in as ${this._username}.`
|
|
|
633
656
|
}
|
|
634
657
|
}
|
|
635
658
|
} catch (error: unknown) {
|
|
636
|
-
if (error
|
|
659
|
+
if ((error as any).name && (error as any).name === 'conflict') {
|
|
637
660
|
logger.warn(`Design doc ${doc._id} update conflict - will retry`);
|
|
638
661
|
// Wait a bit and try again
|
|
639
662
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -8,6 +8,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';
|
|
11
|
+
import { getDbPath } from '../../util/dataDirectory';
|
|
11
12
|
|
|
12
13
|
const log = (s: any) => {
|
|
13
14
|
logger.info(s);
|
|
@@ -94,7 +95,16 @@ export function getLocalUserDB(username: string): PouchDB.Database {
|
|
|
94
95
|
// adapter = 'memory';
|
|
95
96
|
// }
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
const dbName = `userdb-${username}`;
|
|
99
|
+
|
|
100
|
+
// Use proper data directory in Node.js, browser will use IndexedDB
|
|
101
|
+
if (typeof window === 'undefined') {
|
|
102
|
+
// Node.js environment - use filesystem with proper app data directory
|
|
103
|
+
return new pouch(getDbPath(dbName), {});
|
|
104
|
+
} else {
|
|
105
|
+
// Browser environment - use default (IndexedDB)
|
|
106
|
+
return new pouch(dbName, {});
|
|
107
|
+
}
|
|
98
108
|
}
|
|
99
109
|
|
|
100
110
|
/**
|
|
@@ -97,6 +97,30 @@ export default class CourseLookup {
|
|
|
97
97
|
return resp.id;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Adds a new course to the lookup database with a specific courseID
|
|
102
|
+
* @param courseId The specific course ID to use
|
|
103
|
+
* @param courseName The course name
|
|
104
|
+
* @param disambiguator Optional disambiguator
|
|
105
|
+
* @returns Promise<void>
|
|
106
|
+
*/
|
|
107
|
+
static async addWithId(
|
|
108
|
+
courseId: string,
|
|
109
|
+
courseName: string,
|
|
110
|
+
disambiguator?: string
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
const doc: Omit<CourseLookupDoc, '_rev'> = {
|
|
113
|
+
_id: courseId,
|
|
114
|
+
name: courseName,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (disambiguator) {
|
|
118
|
+
doc.disambiguator = disambiguator;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await CourseLookup._db.put(doc);
|
|
122
|
+
}
|
|
123
|
+
|
|
100
124
|
/**
|
|
101
125
|
* Removes a course from the index
|
|
102
126
|
* @param courseID
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Test suite for cross-platform data directory utilities
|
|
2
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { getAppDataDirectory, getDbPath, ensureAppDataDirectory } from './dataDirectory';
|
|
6
|
+
|
|
7
|
+
describe('dataDirectory utilities', () => {
|
|
8
|
+
const originalHomedir = os.homedir;
|
|
9
|
+
const testHome = '/test/home';
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// Mock os.homedir for consistent testing
|
|
13
|
+
(os as any).homedir = () => testHome;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
// Restore original function
|
|
18
|
+
os.homedir = originalHomedir;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('getAppDataDirectory', () => {
|
|
22
|
+
it('should return correct path using home directory', () => {
|
|
23
|
+
const result = getAppDataDirectory();
|
|
24
|
+
expect(result).toBe(path.join(testHome, '.tuilder'));
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('getDbPath', () => {
|
|
29
|
+
it('should return correct database path', () => {
|
|
30
|
+
const dbName = 'userdb-testuser';
|
|
31
|
+
const result = getDbPath(dbName);
|
|
32
|
+
expect(result).toBe(path.join(testHome, '.tuilder', dbName));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should handle special characters in username', () => {
|
|
36
|
+
const dbName = 'userdb-test@user.com';
|
|
37
|
+
const result = getDbPath(dbName);
|
|
38
|
+
expect(result).toBe(path.join(testHome, '.tuilder', dbName));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('ensureAppDataDirectory', () => {
|
|
43
|
+
it('should handle path creation gracefully', async () => {
|
|
44
|
+
// Note: This test doesn't actually create directories since we're testing logic
|
|
45
|
+
// In a real test environment, you'd want to use a temp directory
|
|
46
|
+
expect(typeof ensureAppDataDirectory).toBe('function');
|
|
47
|
+
|
|
48
|
+
// Test the function exists and returns a promise
|
|
49
|
+
const result = ensureAppDataDirectory();
|
|
50
|
+
expect(result).toBeInstanceOf(Promise);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Cross-platform data directory utilities for PouchDB
|
|
2
|
+
// Provides OS-appropriate application data directories
|
|
3
|
+
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import { logger } from './tuiLogger';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get the application data directory for the current platform
|
|
11
|
+
* Uses ~/.tuilder as requested by user for simplicity
|
|
12
|
+
*/
|
|
13
|
+
export function getAppDataDirectory(): string {
|
|
14
|
+
return path.join(os.homedir(), '.tuilder');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Ensure the application data directory exists
|
|
19
|
+
* Creates directory recursively if it doesn't exist
|
|
20
|
+
*/
|
|
21
|
+
export async function ensureAppDataDirectory(): Promise<string> {
|
|
22
|
+
const appDataDir = getAppDataDirectory();
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await fs.promises.mkdir(appDataDir, { recursive: true });
|
|
26
|
+
} catch (err: any) {
|
|
27
|
+
if (err.code !== 'EEXIST') {
|
|
28
|
+
throw new Error(`Failed to create app data directory ${appDataDir}: ${err.message}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return appDataDir;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the full path for a PouchDB database file
|
|
37
|
+
* @param dbName - The database name (e.g., 'userdb-Colin')
|
|
38
|
+
*/
|
|
39
|
+
export function getDbPath(dbName: string): string {
|
|
40
|
+
return path.join(getAppDataDirectory(), dbName);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Initialize data directory for PouchDB usage
|
|
45
|
+
* Should be called once at application startup
|
|
46
|
+
*/
|
|
47
|
+
export async function initializeDataDirectory(): Promise<void> {
|
|
48
|
+
await ensureAppDataDirectory();
|
|
49
|
+
|
|
50
|
+
// Log initialization
|
|
51
|
+
logger.info(`PouchDB data directory initialized: ${getAppDataDirectory()}`);
|
|
52
|
+
}
|
package/src/util/index.ts
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// packages/db/src/util/migrator/FileSystemAdapter.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Abstraction for file system operations needed by the migrator.
|
|
5
|
+
* This allows dependency injection of file system operations,
|
|
6
|
+
* avoiding bundling issues with Node.js fs module.
|
|
7
|
+
*/
|
|
8
|
+
export interface FileSystemAdapter {
|
|
9
|
+
/**
|
|
10
|
+
* Read a text file and return its contents as a string
|
|
11
|
+
*/
|
|
12
|
+
readFile(filePath: string): Promise<string>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Read a binary file and return its contents as a Buffer
|
|
16
|
+
*/
|
|
17
|
+
readBinary(filePath: string): Promise<Buffer>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if a file or directory exists
|
|
21
|
+
*/
|
|
22
|
+
exists(filePath: string): Promise<boolean>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get file/directory statistics
|
|
26
|
+
*/
|
|
27
|
+
stat(filePath: string): Promise<FileStats>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Join path segments into a complete path
|
|
31
|
+
*/
|
|
32
|
+
joinPath(...segments: string[]): string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if a path is absolute
|
|
36
|
+
*/
|
|
37
|
+
isAbsolute(filePath: string): boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface FileStats {
|
|
41
|
+
isDirectory(): boolean;
|
|
42
|
+
isFile(): boolean;
|
|
43
|
+
size: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Error thrown when file system operations fail
|
|
48
|
+
*/
|
|
49
|
+
export class FileSystemError extends Error {
|
|
50
|
+
constructor(
|
|
51
|
+
message: string,
|
|
52
|
+
public readonly operation: string,
|
|
53
|
+
public readonly filePath: string,
|
|
54
|
+
public readonly cause?: Error
|
|
55
|
+
) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.name = 'FileSystemError';
|
|
58
|
+
}
|
|
59
|
+
}
|