Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
32 changes: 31 additions & 1 deletion common-react/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,20 @@ __metadata:
languageName: unknown
linkType: soft

"@dbeaver/table-data@workspace:../common-typescript/@dbeaver/table-data":
version: 0.0.0-use.local
resolution: "@dbeaver/table-data@workspace:../common-typescript/@dbeaver/table-data"
dependencies:
"@dbeaver/cli": "workspace:^"
"@dbeaver/tsconfig": "workspace:^"
async-mutex: "npm:^0"
nanoevents: "npm:^9"
rimraf: "npm:^6"
typescript: "npm:^5"
vitest: "npm:^3"
languageName: unknown
linkType: soft

"@dbeaver/tsconfig@workspace:../common-typescript/@dbeaver/tsconfig, @dbeaver/tsconfig@workspace:^":
version: 0.0.0-use.local
resolution: "@dbeaver/tsconfig@workspace:../common-typescript/@dbeaver/tsconfig"
Expand Down Expand Up @@ -1988,6 +2002,15 @@ __metadata:
languageName: node
linkType: hard

"async-mutex@npm:^0":
version: 0.5.0
resolution: "async-mutex@npm:0.5.0"
dependencies:
tslib: "npm:^2.4.0"
checksum: 10c0/9096e6ad6b674c894d8ddd5aa4c512b09bb05931b8746ebd634952b05685608b2b0820ed5c406e6569919ff5fe237ab3c491e6f2887d6da6b6ba906db3ee9c32
languageName: node
linkType: hard

"axe-core@npm:^4.10.2":
version: 4.10.3
resolution: "axe-core@npm:4.10.3"
Expand Down Expand Up @@ -5127,6 +5150,13 @@ __metadata:
languageName: node
linkType: hard

"nanoevents@npm:^9":
version: 9.1.0
resolution: "nanoevents@npm:9.1.0"
checksum: 10c0/5fb48e6fc1d3102daddaeffd6eada907c25b1c8554dcc648e9cb0a72979a1ab3ee56ffa2d2b3e566a8e561a9e2992a3783493c61cfaaa096b2986d56dbbc1ca5
languageName: node
linkType: hard

"nanoid@npm:^3.3.8":
version: 3.3.11
resolution: "nanoid@npm:3.3.11"
Expand Down Expand Up @@ -6512,7 +6542,7 @@ __metadata:
languageName: node
linkType: hard

"tslib@npm:^2, tslib@npm:^2.3.0":
"tslib@npm:^2, tslib@npm:^2.3.0, tslib@npm:^2.4.0":
version: 2.8.1
resolution: "tslib@npm:2.8.1"
checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
Expand Down
1 change: 1 addition & 0 deletions common-typescript/@dbeaver/table-data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @dbeaver/table-data
40 changes: 40 additions & 0 deletions common-typescript/@dbeaver/table-data/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "@dbeaver/table-data",
"type": "module",
"sideEffects": [],
"exports": {
".": "./lib/index.js",
"./*": "./lib/*.js"
},
"scripts": {
"clear": "rimraf lib",
"build": "tsc -b",
"test": "dbeaver-test"
},
"files": [
"package.json",
"LICENSE",
"README.md",
"CHANGELOG.md",
"lib",
"!lib/**/*.d.ts.map",
"!lib/**/*.test.js",
"!lib/**/*.test.d.ts",
"!lib/**/*.test.d.ts.map",
"!lib/**/*.test.js.map",
"!lib/tests",
"!.tsbuildinfo"
],
"packageManager": "[email protected]",
"devDependencies": {
"@dbeaver/cli": "workspace:^",
"@dbeaver/tsconfig": "workspace:^",
"rimraf": "^6",
"typescript": "^5",
"vitest": "^3"
},
"dependencies": {
"async-mutex": "^0",
"nanoevents": "^9"
}
}
18 changes: 18 additions & 0 deletions common-typescript/@dbeaver/table-data/src/TableDatasetManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ITableDataset } from './interfaces/ITableDataset.js';
import type { ITableDatasetManager } from './interfaces/ITableDatasetManager.js';

export class TableDatasetManager<TColumn, TValue> implements ITableDatasetManager<TColumn, TValue> {
get datasets(): ITableDataset<TColumn, TValue>[] {
return this.#datasets;
}

#datasets: ITableDataset<TColumn, TValue>[] = [];

constructor() {
this.#datasets = [];
}

setDatasets(datasets: ITableDataset<TColumn, TValue>[]): void {
this.#datasets = datasets;
}
}
110 changes: 110 additions & 0 deletions common-typescript/@dbeaver/table-data/src/TableSource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Mutex } from 'async-mutex';
import type { ITableSource } from './interfaces/ITableSource.js';
import { createNanoEvents, type Emitter, type Unsubscribe } from 'nanoevents';
import type { TableSourceEvents } from './interfaces/TableSourceEvents.js';
import type { ITableDatasetManager } from './interfaces/ITableDatasetManager.js';
import type { ITableSourceOptions } from './interfaces/ITableSourceOptions.js';

export abstract class TableSource<TColumn, TValue, TOptions extends ITableSourceOptions = ITableSourceOptions>
implements ITableSource<TColumn, TValue, TOptions>
{
get isLoading(): boolean {
return this.#loadingInProgress;
}
get isOutdated(): boolean {
return this.#outdated;
}
error: Error | null;
options: Partial<TOptions>;

#outdated: boolean;
#loadingInProgress = false;
#loadingPending = false;
readonly #mutex: Mutex;
readonly #emitter: Emitter<TableSourceEvents>;
constructor(readonly datasetManager: ITableDatasetManager<TColumn, TValue>) {
this.options = {};
this.error = null;
this.#outdated = false;
this.#mutex = new Mutex();
this.#emitter = createNanoEvents();
}

setOptions(options: TOptions): void {
this.options = options;
this.setOutdated();
}

setOutdated(): void {
this.setOutdatedValue(true);
if (this.#mutex.isLocked()) {
this.#mutex.runExclusive(() => {
this.setOutdatedValue(true);
});
}
}

async save(): Promise<void> {
const release = await this.#mutex.acquire();
try {
this.setError(null);
await this.saveData();
this.emit('saved');
} catch (error) {
this.setError(error instanceof Error ? error : new Error(String(error)));
throw this.error;
} finally {
release();
}
}

async load(): Promise<void> {
if (this.#loadingInProgress) {
this.#loadingPending = true;
return await this.#mutex.waitForUnlock();
}
this.setLoadingInProgress(true);
try {
// this waits for any save to finish, then runs loadData
await this.#mutex.runExclusive(async () => {
do {
this.#loadingPending = false;
this.setError(null);
await this.loadData();
this.emit('data');
} while (this.#loadingPending);
});
} catch (error) {
this.setError(error instanceof Error ? error : new Error(String(error)));
throw this.error;
} finally {
this.setOutdatedValue(false);
this.setLoadingInProgress(false);
}
}

on<TEvent extends keyof TableSourceEvents>(event: TEvent, listener: TableSourceEvents[TEvent]): Unsubscribe {
return this.#emitter.on(event, listener);
}

protected setLoadingInProgress(state: boolean): void {
this.emit('loading', state);
this.#loadingInProgress = state;
}

protected setError(error: Error | null): void {
this.error = error;
this.emit('error', error);
}

protected setOutdatedValue(state: boolean): void {
this.#outdated = state;
this.emit('outdated', state);
}

protected emit<TEvent extends keyof TableSourceEvents>(event: TEvent, ...args: Parameters<TableSourceEvents[TEvent]>): void {
this.#emitter.emit(event, ...args);
}
protected abstract saveData(): Promise<void>;
protected abstract loadData(): Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Cell position types
export interface ICellPosition {
readonly rowIdx: number;
readonly colIdx: number;
}
27 changes: 27 additions & 0 deletions common-typescript/@dbeaver/table-data/src/editor/ITableEditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { ICellPosition } from './ICellPosition.js';
import type { ITableEditorHistory } from './ITableEditorHistory.js';
import type { TableEditorEventEmitter } from './TableEditorEvents.js';

// Main table editor interface
export interface ITableEditor<TValue> extends TableEditorEventEmitter<TValue> {
// Data access
readonly data: readonly (readonly TValue[])[];
readonly isEdited: boolean;
readonly rowCount: number;

// Cell operations
getCellValue(position: ICellPosition): TValue | undefined;
setCellValue(position: ICellPosition, value: TValue): void;

// Row operations
insertRow(rowIdx: number, rowData?: TValue[]): void;
deleteRow(rowIdx: number): void;

// Data operations
resetData(newData: TValue[][]): void;

// History operations
readonly history: ITableEditorHistory<TValue>;
undo(): boolean;
redo(): boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ITableEditor } from './ITableEditor.js';

export interface ITableEditorFactory {
create<TValue>(initialData?: TValue[][]): ITableEditor<TValue>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { ICellPosition } from './ICellPosition.js';

// History entry types
export interface ITableEditorHistoryEntry<TValue> {
readonly type: 'cell-edit' | 'row-insert' | 'row-delete';
readonly timestamp: number;
readonly data: ICellEditEntry<TValue> | IRowInsertEntry<TValue> | IRowDeleteEntry<TValue>;
}

export interface ICellEditEntry<TValue> {
readonly position: ICellPosition;
readonly oldValue: TValue;
readonly newValue: TValue;
}

export interface IRowInsertEntry<TValue> {
readonly rowIdx: number;
readonly rowData: TValue[];
}

export interface IRowDeleteEntry<TValue> {
readonly rowIdx: number;
readonly rowData: TValue[];
}

// History management interface
export interface ITableEditorHistory<TValue> {
readonly canUndo: boolean;
readonly canRedo: boolean;
readonly size: number;
readonly maxSize: number;

push(entry: ITableEditorHistoryEntry<TValue>): void;
undo(): ITableEditorHistoryEntry<TValue> | null;
redo(): ITableEditorHistoryEntry<TValue> | null;
clear(): void;
setMaxSize(size: number): void;
}
Loading
Loading