Skip to content

Commit e90412e

Browse files
committed
feat(save-user-data): create a user data abstract class
1 parent 120fab3 commit e90412e

File tree

12 files changed

+99
-67
lines changed

12 files changed

+99
-67
lines changed

packages/atlas-service/src/secret-store.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { UserData, z } from '@mongodb-js/compass-user-data';
1+
import { FileUserData, z } from '@mongodb-js/compass-user-data';
22
import { safeStorage } from 'electron';
33

44
const AtlasPluginStateSchema = z.string().optional();
55

66
export class SecretStore {
7-
private readonly userData: UserData<typeof AtlasPluginStateSchema>;
7+
private readonly userData: FileUserData<typeof AtlasPluginStateSchema>;
88
private readonly fileName = 'AtlasPluginState';
99
constructor(basePath?: string) {
10-
this.userData = new UserData(AtlasPluginStateSchema, {
10+
this.userData = new FileUserData(AtlasPluginStateSchema, {
1111
subdir: 'AtlasState',
1212
basePath,
1313
});

packages/compass-data-modeling/src/components/diagram-card.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ describe('DiagramCard', () => {
4242
render(<DiagramCard {...props} />);
4343
expect(screen.getByText('Test Diagram')).to.be.visible;
4444
expect(screen.getByText('someDatabase')).to.be.visible;
45-
expect(screen.getByText('Last modified: October 3, 2023')).to.be.visible;
45+
expect(screen.getByText(/modified/)).to.be.visible;
4646
});
4747
});

packages/compass-data-modeling/src/services/data-model-storage-electron.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { UserData } from '@mongodb-js/compass-user-data';
2+
import { FileUserData } from '@mongodb-js/compass-user-data';
33
import type {
44
DataModelStorage,
55
MongoDBDataModelDescription,
@@ -8,9 +8,11 @@ import { MongoDBDataModelDescriptionSchema } from './data-model-storage';
88
import { DataModelStorageServiceProvider } from '../provider';
99

1010
class DataModelStorageElectron implements DataModelStorage {
11-
private readonly userData: UserData<typeof MongoDBDataModelDescriptionSchema>;
11+
private readonly userData: FileUserData<
12+
typeof MongoDBDataModelDescriptionSchema
13+
>;
1214
constructor(basePath?: string) {
13-
this.userData = new UserData(MongoDBDataModelDescriptionSchema, {
15+
this.userData = new FileUserData(MongoDBDataModelDescriptionSchema, {
1416
subdir: 'DataModelDescriptions',
1517
basePath,
1618
});

packages/compass-preferences-model/src/preferences-persistent-storage.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type z, UserData } from '@mongodb-js/compass-user-data';
1+
import { type z, FileUserData } from '@mongodb-js/compass-user-data';
22
import {
33
getDefaultsForStoredPreferences,
44
getPreferencesValidator,
@@ -20,12 +20,12 @@ export type PreferencesSafeStorage = {
2020
export class PersistentStorage implements PreferencesStorage {
2121
private readonly file = 'General';
2222
private readonly defaultPreferences = getDefaultsForStoredPreferences();
23-
private readonly userData: UserData<StoredPreferencesValidator>;
23+
private readonly userData: FileUserData<StoredPreferencesValidator>;
2424
private preferences: StoredPreferences = getDefaultsForStoredPreferences();
2525
private safeStorage?: PreferencesSafeStorage;
2626

2727
constructor(basePath?: string, safeStorage?: PreferencesSafeStorage) {
28-
this.userData = new UserData(getPreferencesValidator(), {
28+
this.userData = new FileUserData(getPreferencesValidator(), {
2929
subdir: 'AppPreferences',
3030
basePath,
3131
});

packages/compass-preferences-model/src/user-storage.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { z } from '@mongodb-js/compass-user-data';
22
import { UUID } from 'bson';
3-
import { UserData } from '@mongodb-js/compass-user-data';
3+
import { FileUserData } from '@mongodb-js/compass-user-data';
44

55
const UserSchema = z.object({
66
id: z.string().uuid(),
@@ -24,9 +24,9 @@ export interface UserStorage {
2424
}
2525

2626
export class UserStorageImpl implements UserStorage {
27-
private readonly userData: UserData<typeof UserSchema>;
27+
private readonly userData: FileUserData<typeof UserSchema>;
2828
constructor(basePath?: string) {
29-
this.userData = new UserData(UserSchema, {
29+
this.userData = new FileUserData(UserSchema, {
3030
subdir: 'Users',
3131
basePath,
3232
});

packages/compass-shell/src/modules/history-storage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { getAppName } from '@mongodb-js/compass-utils';
2-
import { UserData, z } from '@mongodb-js/compass-user-data';
2+
import { FileUserData, z } from '@mongodb-js/compass-user-data';
33

44
export class HistoryStorage {
55
fileName = 'shell-history';
66
userData;
77

88
constructor(basePath?: string) {
9-
this.userData = new UserData(z.string().array(), {
9+
this.userData = new FileUserData(z.string().array(), {
1010
// Todo: https://jira.mongodb.org/browse/COMPASS-7080
1111
subdir: getAppName() ?? '',
1212
basePath,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export type { Stats, ReadAllResult, ReadAllWithStatsResult } from './user-data';
2-
export { UserData } from './user-data';
2+
export { IUserData, FileUserData } from './user-data';
33
export { z } from 'zod';

packages/compass-user-data/src/user-data.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Stats } from 'fs';
33
import os from 'os';
44
import path from 'path';
55
import { expect } from 'chai';
6-
import { UserData, type UserDataOptions } from './user-data';
6+
import { FileUserData, type FileUserDataOptions } from './user-data';
77
import { z, type ZodError } from 'zod';
88

99
type ValidatorOptions = {
@@ -44,11 +44,11 @@ describe('user-data', function () {
4444

4545
const getUserData = (
4646
userDataOpts: Partial<
47-
UserDataOptions<z.input<ReturnType<typeof getTestSchema>>>
47+
FileUserDataOptions<z.input<ReturnType<typeof getTestSchema>>>
4848
> = {},
4949
validatorOpts: ValidatorOptions = {}
5050
) => {
51-
return new UserData(getTestSchema(validatorOpts), {
51+
return new FileUserData(getTestSchema(validatorOpts), {
5252
subdir,
5353
basePath: tmpDir,
5454
...userDataOpts,

packages/compass-user-data/src/user-data.ts

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,19 @@ type SerializeContent<I> = (content: I) => string;
1212
type DeserializeContent = (content: string) => unknown;
1313
type GetFileName = (id: string) => string;
1414

15-
export type UserDataOptions<Input> = {
15+
export type FileUserDataOptions<Input> = {
1616
subdir: string;
1717
basePath?: string;
1818
serialize?: SerializeContent<Input>;
1919
deserialize?: DeserializeContent;
2020
getFileName?: GetFileName;
2121
};
2222

23+
export type AtlasUserDataOptions<Input> = {
24+
serialize?: SerializeContent<Input>;
25+
deserialize?: DeserializeContent;
26+
};
27+
2328
type ReadOptions = {
2429
ignoreErrors: boolean;
2530
};
@@ -63,28 +68,54 @@ export interface ReadAllWithStatsResult<T extends z.Schema> {
6368
errors: Error[];
6469
}
6570

66-
export class UserData<T extends z.Schema> {
71+
export abstract class IUserData<T extends z.Schema> {
72+
protected readonly validator: T;
73+
protected readonly serialize: SerializeContent<z.input<T>>;
74+
protected readonly deserialize: DeserializeContent;
75+
protected static readonly semaphore = new Semaphore(100);
76+
77+
constructor(
78+
validator: T,
79+
{
80+
serialize = (content: z.input<T>) => JSON.stringify(content, null, 2),
81+
deserialize = JSON.parse,
82+
}: {
83+
serialize?: SerializeContent<z.input<T>>;
84+
deserialize?: DeserializeContent;
85+
} = {}
86+
) {
87+
this.validator = validator;
88+
this.serialize = serialize;
89+
this.deserialize = deserialize;
90+
}
91+
92+
abstract write(id: string, content: z.input<T>): Promise<boolean>;
93+
abstract delete(id: string): Promise<boolean>;
94+
abstract readAll(options?: ReadOptions): Promise<ReadAllResult<T>>;
95+
abstract updateAttributes(
96+
id: string,
97+
data: Partial<z.input<T>>
98+
): Promise<z.output<T>>;
99+
}
100+
101+
export class FileUserData<T extends z.Schema> extends IUserData<T> {
67102
private readonly subdir: string;
68103
private readonly basePath?: string;
69-
private readonly serialize: SerializeContent<z.input<T>>;
70-
private readonly deserialize: DeserializeContent;
71104
private readonly getFileName: GetFileName;
72-
private readonly semaphore = new Semaphore(100);
73105

74106
constructor(
75-
private readonly validator: T,
107+
validator: T,
76108
{
77109
subdir,
78110
basePath,
79111
serialize = (content: z.input<T>) => JSON.stringify(content, null, 2),
80112
deserialize = JSON.parse,
81113
getFileName = (id) => `${id}.json`,
82-
}: UserDataOptions<z.input<T>>
114+
}: FileUserDataOptions<z.input<T>>
83115
) {
116+
super(validator, { serialize, deserialize });
84117
this.subdir = subdir;
85118
this.basePath = basePath;
86-
this.deserialize = deserialize;
87-
this.serialize = serialize;
88119
this.getFileName = getFileName;
89120
}
90121

@@ -126,7 +157,7 @@ export class UserData<T extends z.Schema> {
126157
let handle: fs.FileHandle | undefined = undefined;
127158
let release: (() => void) | undefined = undefined;
128159
try {
129-
release = await this.semaphore.waitForRelease();
160+
release = await IUserData.semaphore.waitForRelease();
130161
handle = await fs.open(absolutePath, 'r');
131162
[stats, data] = await Promise.all([
132163
handle.stat(),
@@ -290,4 +321,15 @@ export class UserData<T extends z.Schema> {
290321
) {
291322
return (await this.readOneWithStats(id, options))?.[0];
292323
}
324+
325+
async updateAttributes(
326+
id: string,
327+
data: Partial<z.input<T>>
328+
): Promise<z.output<T>> {
329+
await this.write(id, {
330+
...((await this.readOne(id)) ?? {}),
331+
...data,
332+
});
333+
return await this.readOne(id);
334+
}
293335
}

packages/connection-storage/src/compass-main-connection-storage.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import type {
2424
ImportConnectionOptions,
2525
ExportConnectionOptions,
2626
} from './import-export-connection';
27-
import { UserData, z } from '@mongodb-js/compass-user-data';
27+
import { FileUserData, z } from '@mongodb-js/compass-user-data';
2828
import type {
2929
ConnectionStorage,
3030
AutoConnectPreferences,
@@ -86,7 +86,7 @@ const ConnectionSchema: z.Schema<ConnectionWithLegacyProps> = z
8686
.passthrough();
8787

8888
class CompassMainConnectionStorage implements ConnectionStorage {
89-
private readonly userData: UserData<typeof ConnectionSchema>;
89+
private readonly userData: FileUserData<typeof ConnectionSchema>;
9090

9191
private readonly version = 1;
9292
private readonly maxAllowedRecentConnections = 10;
@@ -95,7 +95,7 @@ class CompassMainConnectionStorage implements ConnectionStorage {
9595
private readonly ipcMain: ConnectionStorageIPCMain,
9696
basePath?: string
9797
) {
98-
this.userData = new UserData(ConnectionSchema, {
98+
this.userData = new FileUserData(ConnectionSchema, {
9999
subdir: 'Connections',
100100
basePath,
101101
});

0 commit comments

Comments
 (0)