Skip to content

Commit

Permalink
feat: enhance data store
Browse files Browse the repository at this point in the history
  • Loading branch information
bytemain committed Oct 11, 2024
1 parent 7377d41 commit 5bdf688
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('InMemoryDataStore', () => {
{ user: 'pebbles', age: 1, active: true },
];

const store = new InMemoryDataStore<(typeof users)[0]>({
const store = new InMemoryDataStore<(typeof users)[0], 'user'>({
id: 'user',
});

Expand All @@ -23,12 +23,12 @@ describe('InMemoryDataStore', () => {

const userBarney = store.get('barney');
expect(userBarney).toEqual(users[0]);
expect(store.size()).toBe(3);
expect(store.count()).toBe(3);
expect(addCount).toBe(3);

const items = store.find({ active: true });
expect(items).toEqual([users[0], users[2]]);
expect(store.size({ active: true })).toBe(2);
expect(store.count({ active: true })).toBe(2);

store.on('updated', (oldValue, newValue) => {
expect(oldValue.user).toBe('barney');
Expand All @@ -46,10 +46,10 @@ interface TestItem {
}

describe('InMemoryDataStore2', () => {
let store: InMemoryDataStore<TestItem>;
let store: InMemoryDataStore<TestItem, 'id'>;

beforeEach(() => {
store = new InMemoryDataStore<TestItem>();
store = new InMemoryDataStore<TestItem, 'id'>();
});

test('should initialize correctly', () => {
Expand Down Expand Up @@ -87,8 +87,8 @@ describe('InMemoryDataStore2', () => {
store.create({ name: 'test1' });
store.create({ name: 'test2' });

expect(store.size()).toBe(2);
expect(store.size({ name: 'test1' })).toBe(1);
expect(store.count()).toBe(2);
expect(store.count({ name: 'test1' })).toBe(1);
});

test('should get item by id', () => {
Expand Down
18 changes: 9 additions & 9 deletions packages/core-common/src/remote-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,16 +345,18 @@ class TerminalService {
由于装饰器的执行是在类实例化之前,所以我们可以在 `GDataStore` 这个装饰器中收集 token,然后将它们加入 Injector 即可:

```ts
const dataStore = [] as [string, DataStoreOptions][];
function generateToken(type: 'global' | 'session', token: string, options?: DataStoreOptions) {
// ...
}

export type GDataStore<T, K = number> = InMemoryDataStore<T, K>;
export function GDataStore(token: string, options?: DataStoreOptions): PropertyDecorator {
dataStore.push([token, options]);
const sym = generateToken('global', token, options);

return Autowired(GDataStore, {
tag: String(token),
return Autowired(sym, {
tag: token,
});
}

export type GDataStore<T> = InMemoryDataStore<T>;
```

用一个闭包中的变量 `dataStore` 来储存,然后在创建 Injector 的时候将所有的 token 放入 injector:
Expand All @@ -363,10 +365,8 @@ export type GDataStore<T> = InMemoryDataStore<T>;
function _injectDataStores(injector: Injector) {
dataStore.forEach(([token, opts]) => {
injector.addProviders({
token: GDataStore,
token,
useValue: new InMemoryDataStore(opts),
tag: String(token),
dropdownForTag: false,
});
});
}
Expand Down
64 changes: 39 additions & 25 deletions packages/core-common/src/remote-service/data-store/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,72 @@ import { Autowired, Injector } from '@opensumi/di';

import { DataStoreOptions, InMemoryDataStore } from './store';

type DataStoreItem = Record<
string,
{
sym: symbol;
options: DataStoreOptions | undefined;
}
>;

const dataStore = {
global: {} as Record<string, DataStoreOptions | undefined>,
session: {} as Record<string, DataStoreOptions | undefined>,
GDataStore: {} as DataStoreItem,
SessionDataStore: {} as DataStoreItem,
} as const;
type DataStoreType = keyof typeof dataStore;

function saveToken(type: 'global' | 'session', token: string, options?: DataStoreOptions) {
function generateToken(type: DataStoreType, token: string, options?: DataStoreOptions) {
if (dataStore[type][token]) {
// 同样的 token 只能被注入一次,options 也以第一次为准
return;
return dataStore[type][token].sym;
}

dataStore[type][token] = options;
const sym = Symbol(`${type}:${token}`);
dataStore[type][token] = {
sym,
options,
};
return sym;
}

export type GDataStore<T> = InMemoryDataStore<T>;
export type GDataStore<
Item extends Record<any, any>,
PrimaryKey = keyof Item,
PrimaryKeyType = Item[PrimaryKey],
> = InMemoryDataStore<Item, PrimaryKey, PrimaryKeyType>;
export function GDataStore(token: string, options?: DataStoreOptions): PropertyDecorator {
saveToken('global', token, options);
const sym = generateToken('GDataStore', token, options);

return Autowired(GDataStore, {
tag: String(token),
});
return Autowired(sym);
}

export type SessionDataStore<T> = InMemoryDataStore<T>;
export type SessionDataStore<
Item extends Record<any, any>,
PrimaryKey = keyof Item,
PrimaryKeyType = Item[PrimaryKey],
> = InMemoryDataStore<Item, PrimaryKey, PrimaryKeyType>;
export function SessionDataStore(token: string, options?: DataStoreOptions): PropertyDecorator {
saveToken('session', token, options);
const sym = generateToken('SessionDataStore', token, options);

return Autowired(SessionDataStore, {
tag: String(token),
});
return Autowired(sym);
}

function _injectDataStores(type: 'global' | 'session', injector: Injector) {
function _injectDataStores(type: DataStoreType, injector: Injector) {
const stores = dataStore[type];
if (stores) {
const token = type === 'global' ? GDataStore : SessionDataStore;

injector.addProviders(
...Object.entries(stores).map(([tag, opts]) => ({
token,
useValue: new InMemoryDataStore(opts),
tag,
dropdownForTag: false,
...Object.values(stores).map((opts) => ({
token: opts.sym,
useValue: new InMemoryDataStore(opts.options),
})),
);
}
}

export function injectGDataStores(injector: Injector) {
_injectDataStores('global', injector);
_injectDataStores('GDataStore', injector);
}

export function injectSessionDataStores(injector: Injector) {
_injectDataStores('session', injector);
_injectDataStores('SessionDataStore', injector);
}
6 changes: 3 additions & 3 deletions packages/core-common/src/remote-service/data-store/select.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isIterable } from '@opensumi/ide-utils';

export type Query = Record<string, any>;
export type Store<T> = Iterable<T> | Record<string, T> | Map<string, T>;
export type Query = Record<any, any>;
export type Store<T> = Iterable<T> | Record<any, T> | Map<any, T>;

function makeMatcher(query: Query) {
const statements = [] as string[];
Expand All @@ -13,7 +13,7 @@ function makeMatcher(query: Query) {
return ${statements.join(' && ')};
`;

return new Function('item', matcher) as (item: Query) => boolean;
return new Function('item', matcher) as (item: any) => boolean;
}

export function select<I, T extends Store<I>>(items: T, query: Query): I[] {
Expand Down
77 changes: 57 additions & 20 deletions packages/core-common/src/remote-service/data-store/store.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
import extend from 'lodash/extend';

import { EventEmitter } from '@opensumi/events';
import { isUndefined } from '@opensumi/ide-utils';

import { select } from './select';

export interface DataStore<Item> {
create(item: Item): Item;
find(query: Record<string, any>): Item[] | undefined;
size(query: Record<string, any>): number;
get(id: string, query?: Record<string, any>): Item | undefined;
update(id: string, item: Partial<Item>): void;
remove(id: string): void;
}
/**
* The query looks like:
* { field: value }
* { field: conditions }
*/
export type Query = Record<string, any>;

export interface DataStoreEvent<Item> extends Record<string, any> {
export interface DataStoreEvent<Item> {
created: [item: Item];
updated: [oldItem: Item, newItem: Item];
removed: [item: Item];

[key: string]: any[];
}

export interface DataStoreOptions {
id?: string;
}

export class InMemoryDataStore<Item> extends EventEmitter<DataStoreEvent<Item>> implements DataStore<Item> {
private store = new Map<string, Item>();
export class InMemoryDataStore<
Item extends Record<any, any>,
PrimaryKey extends keyof Item,
PrimaryKeyType = Item[PrimaryKey],
> extends EventEmitter<DataStoreEvent<Item>> {
private store = new Map<PrimaryKeyType, Item>();
private _uid = 0;
/**
* primary key
Expand All @@ -37,7 +42,7 @@ export class InMemoryDataStore<Item> extends EventEmitter<DataStoreEvent<Item>>
}

create(item: Item): Item {
const id = item[this.id] || String(this._uid++);
const id = item[this.id] || this._uid++;
const result = extend({}, item, { [this.id]: id }) as Item;

this.store.set(id, result);
Expand All @@ -46,29 +51,32 @@ export class InMemoryDataStore<Item> extends EventEmitter<DataStoreEvent<Item>>
return result;
}

find(query: Record<string, any>): Item[] | undefined {
find(query?: Query): Item[] | undefined {
if (isUndefined(query)) {
return Array.from(this.store.values());
}
return select(this.store, query);
}

size(query?: Record<string, any>): number {
if (!query) {
count(query?: Query): number {
if (isUndefined(query)) {
return this.store.size;
}

return this.find(query)?.length || 0;
}

get(id: string): Item | undefined {
get(id: PrimaryKeyType): Item | undefined {
return this.store.get(id);
}

has(id: string): boolean {
has(id: PrimaryKeyType): boolean {
return this.store.has(id);
}

update(id: string, item: Partial<Item>): void {
update(id: PrimaryKeyType, item: Partial<Item>): void {
const current = this.store.get(id);
if (!current) {
if (isUndefined(current)) {
return;
}

Expand All @@ -78,12 +86,41 @@ export class InMemoryDataStore<Item> extends EventEmitter<DataStoreEvent<Item>>
this.store.set(id, result);
}

remove(id: string): void {
remove(id: PrimaryKeyType): void {
const item = this.store.get(id);
if (item) {
this.emit('removed', item);
}

this.store.delete(id);
}

removeItem(item: Item): void {
const id = item[this.id] as PrimaryKeyType;
if (isUndefined(id)) {
return;
}

this.remove(id);
}

removeAll(query?: Query): void {
if (isUndefined(query)) {
const items = Array.from(this.store.values());
this.store.clear();

items.forEach((item) => {
this.emit('removed', item);
});
return;
}

const items = this.find(query);
if (items) {
items.forEach((item) => {
this.store.delete(item[this.id]);
this.emit('removed', item);
});
}
}
}
4 changes: 4 additions & 0 deletions packages/utils/src/disposable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,10 @@ export class RefCountedDisposable {

constructor(private readonly _disposable: IDisposable) {}

get disposed() {
return this._counter <= 0;
}

acquire() {
this._counter++;
return this;
Expand Down
4 changes: 1 addition & 3 deletions packages/utils/src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1045,9 +1045,7 @@ export class Dispatcher<T = void> implements IDisposable {
}

dispose(): void {
if (this._emitter) {
this._emitter.dispose();
}
this._emitter.dispose();
}
}

Expand Down

0 comments on commit 5bdf688

Please sign in to comment.