@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.
Files changed (48) hide show
  1. package/dist/core/index.d.mts +5 -5
  2. package/dist/core/index.d.ts +5 -5
  3. package/dist/core/index.js +717 -667
  4. package/dist/core/index.js.map +1 -1
  5. package/dist/core/index.mjs +702 -653
  6. package/dist/core/index.mjs.map +1 -1
  7. package/dist/{dataLayerProvider-BuntXkCs.d.ts → dataLayerProvider-6stCgDME.d.ts} +1 -1
  8. package/dist/{dataLayerProvider-BZmLyBVw.d.mts → dataLayerProvider-BbW9EnZK.d.mts} +1 -1
  9. package/dist/impl/couch/index.d.mts +3 -3
  10. package/dist/impl/couch/index.d.ts +3 -3
  11. package/dist/impl/couch/index.js +1940 -1873
  12. package/dist/impl/couch/index.js.map +1 -1
  13. package/dist/impl/couch/index.mjs +1894 -1828
  14. package/dist/impl/couch/index.mjs.map +1 -1
  15. package/dist/impl/static/index.d.mts +4 -4
  16. package/dist/impl/static/index.d.ts +4 -4
  17. package/dist/impl/static/index.js +557 -507
  18. package/dist/impl/static/index.js.map +1 -1
  19. package/dist/impl/static/index.mjs +575 -526
  20. package/dist/impl/static/index.mjs.map +1 -1
  21. package/dist/index.d.mts +244 -8
  22. package/dist/index.d.ts +244 -8
  23. package/dist/index.js +3922 -2792
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mjs +3897 -2781
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/{types-D6SnlHPm.d.ts → types-BvzcRAys.d.ts} +1 -1
  28. package/dist/{types-DPRvCrIk.d.mts → types-CQQ80R5N.d.mts} +1 -1
  29. package/dist/{types-legacy-WPe8CtO-.d.mts → types-legacy-CtrmkOLu.d.mts} +1 -1
  30. package/dist/{types-legacy-WPe8CtO-.d.ts → types-legacy-CtrmkOLu.d.ts} +1 -1
  31. package/dist/{userDB-31gsvxyd.d.mts → userDB-7fM4tpgr.d.mts} +2 -2
  32. package/dist/{userDB-D9EuWTp1.d.ts → userDB-DUY63VMN.d.ts} +2 -2
  33. package/dist/util/packer/index.d.mts +3 -3
  34. package/dist/util/packer/index.d.ts +3 -3
  35. package/package.json +2 -2
  36. package/src/factory.ts +25 -0
  37. package/src/impl/common/BaseUserDB.ts +29 -6
  38. package/src/impl/common/userDBHelpers.ts +11 -1
  39. package/src/impl/couch/courseLookupDB.ts +24 -0
  40. package/src/util/dataDirectory.test.ts +53 -0
  41. package/src/util/dataDirectory.ts +52 -0
  42. package/src/util/index.ts +3 -0
  43. package/src/util/migrator/FileSystemAdapter.ts +59 -0
  44. package/src/util/migrator/StaticToCouchDBMigrator.ts +707 -0
  45. package/src/util/migrator/index.ts +18 -0
  46. package/src/util/migrator/types.ts +84 -0
  47. package/src/util/migrator/validation.ts +517 -0
  48. package/src/util/tuiLogger.ts +139 -0
@@ -1,5 +1,5 @@
1
1
  import { CourseConfig } from '@vue-skuilder/common';
2
- import { D as DocType } from './types-legacy-WPe8CtO-.js';
2
+ import { D as DocType } from './types-legacy-CtrmkOLu.js';
3
3
 
4
4
  interface StaticCourseManifest {
5
5
  version: string;
@@ -1,5 +1,5 @@
1
1
  import { CourseConfig } from '@vue-skuilder/common';
2
- import { D as DocType } from './types-legacy-WPe8CtO-.mjs';
2
+ import { D as DocType } from './types-legacy-CtrmkOLu.mjs';
3
3
 
4
4
  interface StaticCourseManifest {
5
5
  version: string;
@@ -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 Tag as T, type TagStub 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 };
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 Tag as T, type TagStub 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 };
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, a as TagStub, T as Tag, g as CardHistory, C as CardRecord } from './types-legacy-WPe8CtO-.mjs';
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 CourseRegistrationDoc as B, type CourseDBInterface as C, type ScheduledCard as D, type DocumentUpdater as E, newInterval as F, type DataLayerResult as G, type ContentNavigationStrategyData as H, type StudySessionItem as S, type TeacherClassroomDBInterface as T, type UserDBInterface as U, type CoursesDBInterface as a, type ClassroomDBInterface as b, type StudyContentSource as c, type StudentClassroomDBInterface as d, type AssignedContent as e, type AssignedTag as f, type AssignedCourse as g, type AssignedCard as h, type StudySessionFailedItem as i, type StudySessionFailedNewItem as j, type StudySessionFailedReviewItem as k, type StudySessionNewItem as l, type StudySessionReviewItem as m, isReview as n, type ContentSourceID as o, getStudySource as p, type CourseInfo as q, type UserCourseSettings as r, type UserCourseSetting as s, type UsrCrsDataInterface as t, type ClassroomRegistrationDesignation as u, type ClassroomRegistration as v, type ClassroomRegistrationDoc as w, type UserConfig as x, type ActivityRecord as y, type CourseRegistration as z };
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, a as TagStub, T as Tag, g as CardHistory, C as CardRecord } from './types-legacy-WPe8CtO-.js';
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 CourseRegistrationDoc as B, type CourseDBInterface as C, type ScheduledCard as D, type DocumentUpdater as E, newInterval as F, type DataLayerResult as G, type ContentNavigationStrategyData as H, type StudySessionItem as S, type TeacherClassroomDBInterface as T, type UserDBInterface as U, type CoursesDBInterface as a, type ClassroomDBInterface as b, type StudyContentSource as c, type StudentClassroomDBInterface as d, type AssignedContent as e, type AssignedTag as f, type AssignedCourse as g, type AssignedCard as h, type StudySessionFailedItem as i, type StudySessionFailedNewItem as j, type StudySessionFailedReviewItem as k, type StudySessionNewItem as l, type StudySessionReviewItem as m, isReview as n, type ContentSourceID as o, getStudySource as p, type CourseInfo as q, type UserCourseSettings as r, type UserCourseSetting as s, type UsrCrsDataInterface as t, type ClassroomRegistrationDesignation as u, type ClassroomRegistration as v, type ClassroomRegistrationDoc as w, type UserConfig as x, type ActivityRecord as y, type CourseRegistration as z };
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-DPRvCrIk.mjs';
2
- export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, S as StaticCourseManifest } from '../../types-DPRvCrIk.mjs';
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-WPe8CtO-.mjs';
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-D6SnlHPm.js';
2
- export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, S as StaticCourseManifest } from '../../types-D6SnlHPm.js';
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-WPe8CtO-.js';
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",
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.6",
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
- localStorage.removeItem('dbUUID');
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
- throw new Error(`Cannot change accounts while logged in.
131
- Log out of account ${this.getUsername()} before logging in as ${username}.`);
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
- localStorage.removeItem('dbUUID');
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 instanceof Error && error.name === 'conflict') {
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
- return new pouch(`userdb-${username}`, {});
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
@@ -1,2 +1,5 @@
1
1
  export * from './Loggable';
2
2
  export * from './packer';
3
+ export * from './migrator';
4
+ export * from './dataDirectory';
5
+ export * from './tuiLogger';
@@ -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
+ }