Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/atlas-service/src/secret-store.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { UserData, z } from '@mongodb-js/compass-user-data';
import { FileUserData, z } from '@mongodb-js/compass-user-data';
import { safeStorage } from 'electron';

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

export class SecretStore {
private readonly userData: UserData<typeof AtlasPluginStateSchema>;
private readonly userData: FileUserData<typeof AtlasPluginStateSchema>;
private readonly fileName = 'AtlasPluginState';
constructor(basePath?: string) {
this.userData = new UserData(AtlasPluginStateSchema, {
this.userData = new FileUserData(AtlasPluginStateSchema, {
subdir: 'AtlasState',
basePath,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ describe('DiagramCard', () => {
render(<DiagramCard {...props} />);
expect(screen.getByText('Test Diagram')).to.be.visible;
expect(screen.getByText('someDatabase')).to.be.visible;
expect(screen.getByText('Last modified: October 3, 2023')).to.be.visible;
expect(screen.getByText(/modified/)).to.be.visible;
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { UserData } from '@mongodb-js/compass-user-data';
import { FileUserData } from '@mongodb-js/compass-user-data';
import type {
DataModelStorage,
MongoDBDataModelDescription,
Expand All @@ -8,9 +8,11 @@ import { MongoDBDataModelDescriptionSchema } from './data-model-storage';
import { DataModelStorageServiceProvider } from '../provider';

class DataModelStorageElectron implements DataModelStorage {
private readonly userData: UserData<typeof MongoDBDataModelDescriptionSchema>;
private readonly userData: FileUserData<
typeof MongoDBDataModelDescriptionSchema
>;
constructor(basePath?: string) {
this.userData = new UserData(MongoDBDataModelDescriptionSchema, {
this.userData = new FileUserData(MongoDBDataModelDescriptionSchema, {
subdir: 'DataModelDescriptions',
basePath,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type z, UserData } from '@mongodb-js/compass-user-data';
import { type z, FileUserData } from '@mongodb-js/compass-user-data';
import {
getDefaultsForStoredPreferences,
getPreferencesValidator,
Expand All @@ -20,12 +20,12 @@ export type PreferencesSafeStorage = {
export class PersistentStorage implements PreferencesStorage {
private readonly file = 'General';
private readonly defaultPreferences = getDefaultsForStoredPreferences();
private readonly userData: UserData<StoredPreferencesValidator>;
private readonly userData: FileUserData<StoredPreferencesValidator>;
private preferences: StoredPreferences = getDefaultsForStoredPreferences();
private safeStorage?: PreferencesSafeStorage;

constructor(basePath?: string, safeStorage?: PreferencesSafeStorage) {
this.userData = new UserData(getPreferencesValidator(), {
this.userData = new FileUserData(getPreferencesValidator(), {
subdir: 'AppPreferences',
basePath,
});
Expand Down
6 changes: 3 additions & 3 deletions packages/compass-preferences-model/src/user-storage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from '@mongodb-js/compass-user-data';
import { UUID } from 'bson';
import { UserData } from '@mongodb-js/compass-user-data';
import { FileUserData } from '@mongodb-js/compass-user-data';

const UserSchema = z.object({
id: z.string().uuid(),
Expand All @@ -24,9 +24,9 @@ export interface UserStorage {
}

export class UserStorageImpl implements UserStorage {
private readonly userData: UserData<typeof UserSchema>;
private readonly userData: FileUserData<typeof UserSchema>;
constructor(basePath?: string) {
this.userData = new UserData(UserSchema, {
this.userData = new FileUserData(UserSchema, {
subdir: 'Users',
basePath,
});
Expand Down
4 changes: 2 additions & 2 deletions packages/compass-shell/src/modules/history-storage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { getAppName } from '@mongodb-js/compass-utils';
import { UserData, z } from '@mongodb-js/compass-user-data';
import { FileUserData, z } from '@mongodb-js/compass-user-data';

export class HistoryStorage {
fileName = 'shell-history';
userData;

constructor(basePath?: string) {
this.userData = new UserData(z.string().array(), {
this.userData = new FileUserData(z.string().array(), {
// Todo: https://jira.mongodb.org/browse/COMPASS-7080
subdir: getAppName() ?? '',
basePath,
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-user-data/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export type { Stats, ReadAllResult, ReadAllWithStatsResult } from './user-data';
export { UserData } from './user-data';
export { IUserData, FileUserData } from './user-data';
export { z } from 'zod';
6 changes: 3 additions & 3 deletions packages/compass-user-data/src/user-data.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Stats } from 'fs';
import os from 'os';
import path from 'path';
import { expect } from 'chai';
import { UserData, type UserDataOptions } from './user-data';
import { FileUserData, type FileUserDataOptions } from './user-data';
import { z, type ZodError } from 'zod';

type ValidatorOptions = {
Expand Down Expand Up @@ -44,11 +44,11 @@ describe('user-data', function () {

const getUserData = (
userDataOpts: Partial<
UserDataOptions<z.input<ReturnType<typeof getTestSchema>>>
FileUserDataOptions<z.input<ReturnType<typeof getTestSchema>>>
> = {},
validatorOpts: ValidatorOptions = {}
) => {
return new UserData(getTestSchema(validatorOpts), {
return new FileUserData(getTestSchema(validatorOpts), {
subdir,
basePath: tmpDir,
...userDataOpts,
Expand Down
62 changes: 52 additions & 10 deletions packages/compass-user-data/src/user-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ type SerializeContent<I> = (content: I) => string;
type DeserializeContent = (content: string) => unknown;
type GetFileName = (id: string) => string;

export type UserDataOptions<Input> = {
export type FileUserDataOptions<Input> = {
subdir: string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming on this one is unfortunate, but I think it's also relevant for the shared userdata interface

basePath?: string;
serialize?: SerializeContent<Input>;
deserialize?: DeserializeContent;
getFileName?: GetFileName;
};

export type AtlasUserDataOptions<Input> = {
serialize?: SerializeContent<Input>;
deserialize?: DeserializeContent;
};

type ReadOptions = {
ignoreErrors: boolean;
};
Expand Down Expand Up @@ -63,28 +68,54 @@ export interface ReadAllWithStatsResult<T extends z.Schema> {
errors: Error[];
}

export class UserData<T extends z.Schema> {
export abstract class IUserData<T extends z.Schema> {
protected readonly validator: T;
protected readonly serialize: SerializeContent<z.input<T>>;
protected readonly deserialize: DeserializeContent;
protected static readonly semaphore = new Semaphore(100);

constructor(
validator: T,
{
serialize = (content: z.input<T>) => JSON.stringify(content, null, 2),
deserialize = JSON.parse,
}: {
serialize?: SerializeContent<z.input<T>>;
deserialize?: DeserializeContent;
} = {}
) {
this.validator = validator;
this.serialize = serialize;
this.deserialize = deserialize;
}

abstract write(id: string, content: z.input<T>): Promise<boolean>;
abstract delete(id: string): Promise<boolean>;
abstract readAll(options?: ReadOptions): Promise<ReadAllResult<T>>;
abstract updateAttributes(
id: string,
data: Partial<z.input<T>>
): Promise<z.output<T>>;
}

export class FileUserData<T extends z.Schema> extends IUserData<T> {
private readonly subdir: string;
private readonly basePath?: string;
private readonly serialize: SerializeContent<z.input<T>>;
private readonly deserialize: DeserializeContent;
private readonly getFileName: GetFileName;
private readonly semaphore = new Semaphore(100);

constructor(
private readonly validator: T,
validator: T,
{
subdir,
basePath,
serialize = (content: z.input<T>) => JSON.stringify(content, null, 2),
deserialize = JSON.parse,
getFileName = (id) => `${id}.json`,
}: UserDataOptions<z.input<T>>
}: FileUserDataOptions<z.input<T>>
) {
super(validator, { serialize, deserialize });
this.subdir = subdir;
this.basePath = basePath;
this.deserialize = deserialize;
this.serialize = serialize;
this.getFileName = getFileName;
}

Expand Down Expand Up @@ -126,7 +157,7 @@ export class UserData<T extends z.Schema> {
let handle: fs.FileHandle | undefined = undefined;
let release: (() => void) | undefined = undefined;
try {
release = await this.semaphore.waitForRelease();
release = await IUserData.semaphore.waitForRelease();
handle = await fs.open(absolutePath, 'r');
[stats, data] = await Promise.all([
handle.stat(),
Expand Down Expand Up @@ -290,4 +321,15 @@ export class UserData<T extends z.Schema> {
) {
return (await this.readOneWithStats(id, options))?.[0];
}

async updateAttributes(
id: string,
data: Partial<z.input<T>>
): Promise<z.output<T>> {
await this.write(id, {
...((await this.readOne(id)) ?? {}),
...data,
});
return await this.readOne(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type {
ImportConnectionOptions,
ExportConnectionOptions,
} from './import-export-connection';
import { UserData, z } from '@mongodb-js/compass-user-data';
import { FileUserData, z } from '@mongodb-js/compass-user-data';
import type {
ConnectionStorage,
AutoConnectPreferences,
Expand Down Expand Up @@ -86,7 +86,7 @@ const ConnectionSchema: z.Schema<ConnectionWithLegacyProps> = z
.passthrough();

class CompassMainConnectionStorage implements ConnectionStorage {
private readonly userData: UserData<typeof ConnectionSchema>;
private readonly userData: FileUserData<typeof ConnectionSchema>;

private readonly version = 1;
private readonly maxAllowedRecentConnections = 10;
Expand All @@ -95,7 +95,7 @@ class CompassMainConnectionStorage implements ConnectionStorage {
private readonly ipcMain: ConnectionStorageIPCMain,
basePath?: string
) {
this.userData = new UserData(ConnectionSchema, {
this.userData = new FileUserData(ConnectionSchema, {
subdir: 'Connections',
basePath,
});
Expand Down
6 changes: 3 additions & 3 deletions packages/my-queries-storage/src/compass-pipeline-storage.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Stats } from '@mongodb-js/compass-user-data';
import { UserData } from '@mongodb-js/compass-user-data';
import { FileUserData } from '@mongodb-js/compass-user-data';
import { PipelineSchema } from './pipeline-storage-schema';
import type { SavedPipeline } from './pipeline-storage-schema';
import type { PipelineStorage } from './pipeline-storage';

export class CompassPipelineStorage implements PipelineStorage {
private readonly userData: UserData<typeof PipelineSchema>;
private readonly userData: FileUserData<typeof PipelineSchema>;
constructor(basePath?: string) {
this.userData = new UserData(PipelineSchema, {
this.userData = new FileUserData(PipelineSchema, {
subdir: 'SavedPipelines',
basePath,
});
Expand Down
52 changes: 20 additions & 32 deletions packages/my-queries-storage/src/compass-query-storage.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,30 @@
import { UUID, EJSON } from 'bson';
import { UserData, type z } from '@mongodb-js/compass-user-data';
import {
RecentQuerySchema,
FavoriteQuerySchema,
type RecentQuery,
type FavoriteQuery,
} from './query-storage-schema';
import { type z } from '@mongodb-js/compass-user-data';
import { type IUserData, FileUserData } from '@mongodb-js/compass-user-data';
import { RecentQuerySchema, FavoriteQuerySchema } from './query-storage-schema';
import type { FavoriteQueryStorage, RecentQueryStorage } from './query-storage';

export type QueryStorageOptions = {
basepath?: string;
};

export interface QueryStorageBackend<TData> {
loadAll(namespace?: string): Promise<TData[]>;
updateAttributes(id: string, data: Partial<TData>): Promise<TData>;
delete(id: string): Promise<boolean>;
saveQuery(data: Omit<TData, '_id' | '_lastExecuted'>): Promise<void>;
}

export abstract class CompassQueryStorage<
TSchema extends z.Schema,
TData extends z.output<TSchema> = z.output<TSchema>
> implements QueryStorageBackend<TData>
{
protected readonly userData: UserData<TSchema>;
export abstract class CompassQueryStorage<TSchema extends z.Schema> {
protected readonly userData: IUserData<TSchema>;
constructor(
schemaValidator: TSchema,
protected readonly folder: string,
protected readonly options: QueryStorageOptions
) {
this.userData = new UserData(schemaValidator, {
// TODO: logic for whether we're in compass web or compass desktop
this.userData = new FileUserData(schemaValidator, {
subdir: folder,
basePath: options.basepath,
serialize: (content) => EJSON.stringify(content, undefined, 2),
deserialize: (content) => EJSON.parse(content),
deserialize: (content: string) => EJSON.parse(content),
});
}

async loadAll(namespace?: string): Promise<TData[]> {
async loadAll(namespace?: string): Promise<z.output<TSchema>[]> {
try {
const { data } = await this.userData.readAll();
const sortedData = data
Expand All @@ -52,23 +38,23 @@ export abstract class CompassQueryStorage<
}
}

async updateAttributes(id: string, data: Partial<TData>): Promise<TData> {
await this.userData.write(id, {
...((await this.userData.readOne(id)) ?? {}),
...data,
});
return await this.userData.readOne(id);
async write(id: string, content: z.input<TSchema>): Promise<boolean> {
return await this.userData.write(id, content);
}

async delete(id: string) {
return await this.userData.delete(id);
}

abstract saveQuery(data: any): Promise<void>;
async updateAttributes(id: string, data: Partial<z.input<TSchema>>) {
return await this.userData.updateAttributes(id, data);
}

abstract saveQuery(data: Partial<z.input<TSchema>>): Promise<void>;
}

export class CompassRecentQueryStorage
extends CompassQueryStorage<typeof RecentQuerySchema, RecentQuery>
extends CompassQueryStorage<typeof RecentQuerySchema>
implements RecentQueryStorage
{
private readonly maxAllowedQueries = 30;
Expand All @@ -88,6 +74,7 @@ export class CompassRecentQueryStorage
}

const _id = new UUID().toString();
// this creates a recent query that we will write to system/db
const recentQuery = {
...data,
_id,
Expand All @@ -98,7 +85,7 @@ export class CompassRecentQueryStorage
}

export class CompassFavoriteQueryStorage
extends CompassQueryStorage<typeof FavoriteQuerySchema, FavoriteQuery>
extends CompassQueryStorage<typeof FavoriteQuerySchema>
implements FavoriteQueryStorage
{
constructor(options: QueryStorageOptions = {}) {
Expand All @@ -112,6 +99,7 @@ export class CompassFavoriteQueryStorage
>
): Promise<void> {
const _id = new UUID().toString();
// this creates a favorite query that we will write to system/db
const favoriteQuery = {
...data,
_id,
Expand Down
Loading