sqlite-wasm-viewer 0.1.0 → 1.0.1

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.
package/src/index.ts DELETED
@@ -1,157 +0,0 @@
1
- import './styles.css';
2
-
3
- import { Database, DbWorkerOutput } from './types';
4
- import { TableView } from './views/TableView/TableView';
5
- import { ExecuteSQLView } from './views/ExecuteSQLView/ExecuteSQLView';
6
- import { QueryRunner } from './QueryRunner';
7
- import { initSqlLogView } from './views/SqlLogView/SqlLogView';
8
- import { DatabaseItem, ExplorerView } from './views/ExplorerView/ExplorerView';
9
- import { collectDbFiles } from './dbScanner';
10
- import { EditCellView } from './views/EditCellView/EditCellView';
11
- import { initState } from './viewerState';
12
-
13
- let viewer: HTMLDivElement | null = null;
14
-
15
- let dbListEl: HTMLDivElement | null = null;
16
-
17
- let tableViewer: TableView | null = null;
18
-
19
- let middlePanel: HTMLDivElement | null = null;
20
-
21
- let rightPanel: HTMLDivElement | null = null;
22
-
23
- let explorerView: ExplorerView | null = null;
24
-
25
- let queryRunner: QueryRunner | null = null;
26
-
27
- type Config = {
28
- isSqliteDatabase: (fileName: string) => boolean;
29
- };
30
-
31
- const defaultSqliteExtension = ['db', 'sqlite'];
32
-
33
- const config: Config = {
34
- isSqliteDatabase: (filename: string) => {
35
- return defaultSqliteExtension.some((ext) =>
36
- filename.endsWith(`.${ext}`)
37
- );
38
- },
39
- };
40
-
41
- export function setConfig(userConfig: Partial<Config>) {
42
- Object.assign(config, userConfig);
43
- Object.freeze(config);
44
- }
45
-
46
- export function showViewer(): void {
47
- if (!viewer) {
48
- viewer = document.createElement('div');
49
- viewer.id = 'viewer';
50
-
51
- initState(viewer);
52
-
53
- const closeBtn = document.createElement('div');
54
- closeBtn.id = 'close_btn';
55
- closeBtn.innerText = 'Close';
56
- closeBtn.onclick = () => {
57
- hideViewer();
58
- };
59
- viewer.appendChild(closeBtn);
60
-
61
- dbListEl = document.createElement('div');
62
- dbListEl.id = 'db_list';
63
-
64
- viewer.appendChild(dbListEl);
65
-
66
- // Middle Panel
67
- middlePanel = document.createElement('div');
68
- middlePanel.id = 'middle_panel';
69
-
70
- const tableViewEl = document.createElement('div');
71
- tableViewEl.id = 'table_view';
72
- middlePanel.appendChild(tableViewEl);
73
-
74
- viewer.append(middlePanel);
75
-
76
- // Right Panel
77
- rightPanel = document.createElement('div');
78
- rightPanel.id = 'right_panel';
79
-
80
- const executeSqlView = new ExecuteSQLView(rightPanel);
81
-
82
- const editCellView = new EditCellView(viewer, rightPanel);
83
-
84
- viewer.append(rightPanel);
85
-
86
- const worker = new Worker(new URL('DbWorker.ts', import.meta.url), {
87
- type: 'module',
88
- });
89
-
90
- explorerView = new ExplorerView(dbListEl);
91
-
92
- const collectDbFilesPromise = collectDbFiles(config.isSqliteDatabase);
93
-
94
- const dbs: { [dbFilepath: string]: DatabaseItem } = {};
95
- worker.onmessage = (message: MessageEvent<DbWorkerOutput>): void => {
96
- if (message.data.type === 'onReady') {
97
- collectDbFilesPromise.then((dbFiles) => {
98
- dbFiles.forEach((dbFile) => {
99
- const dbFilepath = dbFile;
100
- const dbName = dbFile;
101
- dbs[dbFilepath] = {
102
- filename: dbName,
103
- tables: [],
104
- };
105
-
106
- worker.postMessage({
107
- type: 'readSchema',
108
- path: dbFile,
109
- });
110
- });
111
- });
112
- } else if (message.data.type === 'onSchema') {
113
- const tables = message.data.schema.map((tableSchema) => {
114
- return tableSchema[0];
115
- });
116
-
117
- dbs[message.data.dbName].tables = tables;
118
-
119
- explorerView?.addDatabaseItem(dbs[message.data.dbName]);
120
- } else if (message.data.type === 'onQuery') {
121
- if (message.data.label === 'tableView') {
122
- tableViewer?.setTableResults(
123
- message.data.result.resultRows || []
124
- );
125
- }
126
- }
127
- };
128
-
129
- const db: Database = {
130
- post: (message) => {
131
- worker.postMessage(message);
132
- },
133
- on: (message) => {
134
- worker.onmessage?.(message);
135
- },
136
- };
137
-
138
- queryRunner = new QueryRunner(db);
139
-
140
- initSqlLogView(rightPanel, queryRunner);
141
-
142
- tableViewer = new TableView(viewer, tableViewEl, queryRunner);
143
-
144
- executeSqlView.setDb(queryRunner);
145
- editCellView.setDb(queryRunner);
146
-
147
- worker.postMessage({ type: 'init' });
148
- }
149
-
150
- document.body.appendChild(viewer);
151
- }
152
-
153
- export function hideViewer(): void {
154
- if (viewer) {
155
- document.body.removeChild(viewer);
156
- }
157
- }
package/src/styles.css DELETED
@@ -1,65 +0,0 @@
1
- #viewer {
2
- position: fixed;
3
- top: 0;
4
- left: 0;
5
- right: 0;
6
- bottom: 0;
7
- background-color: whitesmoke;
8
- display: flex;
9
- padding: 10px;
10
- padding-top: 20px;
11
- gap: 8px;
12
- }
13
-
14
- #viewer * {
15
- all: revert;
16
- }
17
-
18
- #viewer .viewHeader {
19
- max-height: 20px;
20
- flex-basis: 20px;
21
- line-height: 1.1rem;
22
- padding: 8px;
23
- display: flex;
24
- align-items: center;
25
- gap: 8px;
26
- background-color: lightgray;
27
- }
28
-
29
- #viewer #close_btn {
30
- position: absolute;
31
- left: 10px;
32
- top: 0px;
33
- cursor: pointer;
34
- }
35
-
36
- #viewer #db_list {
37
- width: 200px;
38
- padding: 5px;
39
- background-color: darkgray;
40
- display: flex;
41
- flex-direction: column;
42
- }
43
-
44
- #viewer #tree_root {
45
- flex-grow: 1;
46
- padding: 5px;
47
- background-color: darkgray;
48
- }
49
-
50
- #viewer #middle_panel {
51
- background-color: darkgray;
52
- flex: 1;
53
- flex-basis: 800px;
54
- display: flex;
55
- flex-direction: column;
56
- }
57
-
58
- #viewer #right_panel {
59
- padding: 5px;
60
- background-color: darkgray;
61
- flex: 1;
62
- display: flex;
63
- flex-direction: column;
64
- gap: 5px;
65
- }
package/src/types.ts DELETED
@@ -1,34 +0,0 @@
1
- export type DbWorkerInput =
2
- | {
3
- readonly type: 'init';
4
- }
5
- | {
6
- readonly type: 'query';
7
- readonly query: {
8
- sql: string;
9
- parameters: ReadonlyArray<unknown>;
10
- };
11
- label?: string;
12
- }
13
- | {
14
- readonly type: 'readSchema';
15
- readonly path: string;
16
- };
17
-
18
- export type DbWorkerOutput =
19
- | { readonly type: 'onReady' }
20
- | { readonly type: 'onSchema'; dbName: string; schema: string[] }
21
- | {
22
- readonly type: 'onQuery';
23
- readonly result: {
24
- resultRows?: any[];
25
- tableName: string;
26
- updates?: { changes: any[]; lastInsertRowid: number };
27
- };
28
- label?: string;
29
- };
30
-
31
- export type Database = {
32
- post: (message: DbWorkerInput) => void;
33
- on: (message: MessageEvent<DbWorkerOutput>) => void;
34
- };
@@ -1,49 +0,0 @@
1
- export interface SelectedCell {
2
- value: string;
3
- tableName: string;
4
- columnName: string;
5
- cellRowId: string;
6
- }
7
-
8
- export class ViewerState {
9
- private static _instance: ViewerState;
10
-
11
- static get instance(): ViewerState {
12
- return ViewerState._instance;
13
- }
14
-
15
- selectedCell: SelectedCell | undefined;
16
-
17
- selectedTable: string;
18
-
19
- hasChanges = false;
20
-
21
- constructor(private viewerElem: HTMLElement) {
22
- ViewerState._instance = this;
23
- }
24
-
25
- setSelectedCell(cell: SelectedCell) {
26
- this.selectedCell = cell;
27
-
28
- const event = new CustomEvent('cellSelected', { detail: cell });
29
- this.viewerElem.dispatchEvent(event);
30
- }
31
-
32
- setSelectedTable(tableName: string) {
33
- this.selectedTable = tableName;
34
-
35
- const event = new CustomEvent('tableSelected', { detail: tableName });
36
- this.viewerElem.dispatchEvent(event);
37
- }
38
-
39
- setHasChanges(hasChanges: boolean) {
40
- this.hasChanges = hasChanges;
41
-
42
- const event = new CustomEvent('hasChanges', { detail: hasChanges });
43
- this.viewerElem.dispatchEvent(event);
44
- }
45
- }
46
-
47
- export function initState(viewerElem: HTMLElement) {
48
- return new ViewerState(viewerElem);
49
- }
@@ -1,74 +0,0 @@
1
- import { QueryRunner } from '../../QueryRunner';
2
- import { ViewerState } from '../../viewerState';
3
-
4
- import './styles.css';
5
-
6
- interface CurrentCell {
7
- tableName: string;
8
- columnName: string;
9
- cellRowId: string;
10
- }
11
-
12
- export class EditCellView {
13
- queryRunner: QueryRunner | undefined;
14
-
15
- textArea: HTMLTextAreaElement;
16
-
17
- currentCell: CurrentCell | undefined;
18
-
19
- constructor(
20
- private viewerElem: HTMLElement,
21
- private rootEl: HTMLDivElement
22
- ) {
23
- this.buildDom();
24
-
25
- viewerElem.addEventListener('cellSelected', (event) => {
26
- const { detail: cell } = event;
27
- this.currentCell = cell;
28
- this.textArea.value = cell.value;
29
- this.textArea.select();
30
- });
31
- }
32
-
33
- private buildDom() {
34
- const container = document.createElement('div');
35
- container.id = 'execute_sql_container';
36
-
37
- const header = document.createElement('div');
38
- header.className = 'viewHeader';
39
- header.innerText = 'Edit Cell';
40
- container.appendChild(header);
41
-
42
- this.textArea = document.createElement('textarea');
43
- this.textArea.id = 'execute_sql_textarea';
44
- container.appendChild(this.textArea);
45
-
46
- const executeBtn = document.createElement('button');
47
- executeBtn.innerText = 'Apply';
48
- executeBtn.onclick = this.handleApplyEdit.bind(this);
49
- container.appendChild(executeBtn);
50
-
51
- this.rootEl.appendChild(container);
52
- }
53
-
54
- private handleApplyEdit() {
55
- if (this.textArea.value) {
56
- if (!ViewerState.instance.hasChanges) {
57
- this.queryRunner?.runQuery({
58
- sql: 'SAVEPOINT "RESTOREPOINT"',
59
- parameters: [],
60
- });
61
- }
62
- this.queryRunner?.runQuery({
63
- sql: `UPDATE ${this.currentCell?.tableName} SET "${this.currentCell?.columnName}"=? WHERE "_rowid_"='${this.currentCell?.cellRowId}'`,
64
- parameters: [this.textArea.value],
65
- });
66
-
67
- ViewerState.instance.setHasChanges(true);
68
- }
69
- }
70
-
71
- setDb(queryRunner: QueryRunner) {
72
- this.queryRunner = queryRunner;
73
- }
74
- }
@@ -1,23 +0,0 @@
1
- #execute_sql_container {
2
- display: flex;
3
- flex-direction: column;
4
- }
5
-
6
- #execute_sql_editor {
7
- position: relative;
8
- background-color: white;
9
- height: 300px;
10
- }
11
-
12
- #execute_sql_textarea {
13
- resize: none;
14
- height: 300px;
15
- background-color: white;
16
- overflow: auto;
17
- white-space: nowrap;
18
- font-size: 10pt;
19
- font-family: monospace;
20
- line-height: 1.5;
21
- tab-size: 2;
22
- caret-color: black;
23
- }
@@ -1,49 +0,0 @@
1
- import './styles.css';
2
-
3
- import { QueryRunner } from 'src/QueryRunner';
4
-
5
- export class ExecuteSQLView {
6
- queryRunner: QueryRunner | undefined;
7
-
8
- textArea: HTMLTextAreaElement;
9
-
10
- highlighting: HTMLElement;
11
-
12
- constructor(private rootEl: HTMLDivElement) {
13
- this.buildDom();
14
- }
15
-
16
- private buildDom() {
17
- const container = document.createElement('div');
18
- container.id = 'execute_sql_container';
19
-
20
- const header = document.createElement('div');
21
- header.className = 'viewHeader';
22
- header.innerText = 'Execute SQL';
23
- container.appendChild(header);
24
-
25
- this.textArea = document.createElement('textarea');
26
- this.textArea.id = 'execute_sql_textarea';
27
- container.appendChild(this.textArea);
28
-
29
- const executeBtn = document.createElement('button');
30
- executeBtn.innerText = 'Execute SQL';
31
- executeBtn.onclick = this.handleExecuteSql.bind(this);
32
- container.appendChild(executeBtn);
33
-
34
- this.rootEl.appendChild(container);
35
- }
36
-
37
- private handleExecuteSql() {
38
- if (this.textArea.value) {
39
- this.queryRunner?.runQuery({
40
- sql: this.textArea.value,
41
- parameters: [],
42
- });
43
- }
44
- }
45
-
46
- setDb(queryRunner: QueryRunner) {
47
- this.queryRunner = queryRunner;
48
- }
49
- }
@@ -1,47 +0,0 @@
1
- #execute_sql_container {
2
- display: flex;
3
- flex-direction: column;
4
- }
5
-
6
- #execute_sql_editor {
7
- position: relative;
8
- background-color: white;
9
- height: 300px;
10
- }
11
-
12
- #execute_sql_textarea, #execute_sql_highlighting {
13
- resize: none;
14
- height: 300px;
15
- background-color: white;
16
- overflow: auto;
17
- white-space: nowrap;
18
- font-size: 10pt;
19
- font-family: monospace;
20
- line-height: 1.5;
21
- tab-size: 2;
22
- caret-color: black;
23
- }
24
-
25
- #execute_sql_highlighting {
26
- z-index: 0;
27
- margin: 0;
28
- padding: 2px;
29
- }
30
-
31
- .highlighting {
32
- color: blue;
33
- }
34
-
35
- .sql-hl-keyword {
36
- color: purple;
37
- /* font-weight: 600; */
38
- }
39
-
40
- .sql-hl-special {
41
- color: black;
42
- }
43
-
44
- .sql-hl-string {
45
- color: red;
46
- /* font-weight: 600; */
47
- }
@@ -1,94 +0,0 @@
1
- import { ViewerState } from '../../viewerState';
2
-
3
- import './styles.css';
4
-
5
- export interface DatabaseItem {
6
- filename: string;
7
- tables: string[];
8
- }
9
-
10
- export class ExplorerView {
11
- private containerEl: HTMLElement;
12
-
13
- private expandedItems: { [dbFilepath: string]: boolean } = {};
14
-
15
- private dbs: DatabaseItem[];
16
-
17
- private selectedItem: HTMLElement | null = null;
18
-
19
- constructor(rootEl: HTMLDivElement) {
20
- this.dbs = [];
21
-
22
- const dbListHeader = document.createElement('div');
23
- dbListHeader.className = 'viewHeader';
24
- dbListHeader.innerText = 'Database List';
25
-
26
- rootEl.appendChild(dbListHeader);
27
-
28
- this.containerEl = document.createElement('div');
29
- this.containerEl.id = 'explorer_tree';
30
- rootEl.appendChild(this.containerEl);
31
- }
32
-
33
- public addDatabaseItem(databaseItem: DatabaseItem): void {
34
- this.dbs.push(databaseItem);
35
-
36
- this.addDbToDom(databaseItem);
37
-
38
- if (this.selectedItem === null) {
39
- const firstTable = document.querySelector(
40
- '#explorer_tree > .table'
41
- ) as HTMLElement | undefined;
42
- if (firstTable) {
43
- this.selectTable(firstTable);
44
- }
45
- }
46
- }
47
-
48
- private addDbToDom(databaseItem: DatabaseItem) {
49
- const dbRoot = document.createDocumentFragment();
50
-
51
- const dbItem = document.createElement('div');
52
- dbItem.innerText = databaseItem.filename;
53
- dbItem.className = 'db';
54
-
55
- const expandArrow = document.createElement('div');
56
- expandArrow.className = 'expand';
57
- expandArrow.innerText = '>';
58
- expandArrow.style.cursor = 'pointer';
59
- expandArrow.onclick = () => {
60
- this.expandedItems[databaseItem.filename] =
61
- !this.expandedItems[databaseItem.filename];
62
-
63
- expandArrow.classList.toggle('expanded');
64
- };
65
- expandArrow.classList.add('expanded');
66
- dbItem.appendChild(expandArrow);
67
-
68
- dbRoot.appendChild(dbItem);
69
- databaseItem.tables.forEach((table) => {
70
- const tableItem = document.createElement('div');
71
- tableItem.innerText = table;
72
- tableItem.className = 'table';
73
- tableItem.onclick = () => {
74
- this.selectTable(tableItem);
75
- };
76
-
77
- dbRoot.appendChild(tableItem);
78
- });
79
-
80
- this.containerEl.appendChild(dbRoot);
81
- }
82
-
83
- private selectTable(tableEl: HTMLElement | null) {
84
- if (tableEl) {
85
- const tableName = tableEl.innerText;
86
-
87
- this.selectedItem?.classList.remove('selected');
88
- tableEl.classList.add('selected');
89
- this.selectedItem = tableEl;
90
-
91
- ViewerState.instance.setSelectedTable(tableName);
92
- }
93
- }
94
- }
@@ -1,30 +0,0 @@
1
- #explorer_tree {
2
- padding: 8px;
3
- padding-left: 20px;
4
- }
5
-
6
- #explorer_tree > .db {
7
- position: relative;
8
- text-overflow: ellipsis;
9
- overflow-y: clip;
10
- }
11
-
12
- #explorer_tree .expand {
13
- position: absolute;
14
- top: 0;
15
- left: -15px;
16
- transition: all .3s ease-in;
17
- }
18
-
19
- #explorer_tree .expanded {
20
- transform: rotate(90deg);
21
- }
22
-
23
- #explorer_tree > .table {
24
- margin-left: 20px;
25
- cursor: pointer;
26
- }
27
-
28
- #explorer_tree > .table.selected {
29
- background-color: rgb(128, 128, 128);
30
- }
@@ -1,37 +0,0 @@
1
- import { Query, QueryRunner } from 'src/QueryRunner';
2
- import './styles.css';
3
-
4
- class SqlLogView {
5
- textArea: HTMLTextAreaElement;
6
-
7
- constructor(rootEl: HTMLDivElement, queryRunner: QueryRunner) {
8
- queryRunner.addListener(this.handleQueryRun.bind(this));
9
-
10
- const container = document.createElement('div');
11
- container.id = 'sql_log_container';
12
-
13
- const header = document.createElement('div');
14
- header.className = 'viewHeader';
15
- header.innerText = 'SQL Log';
16
-
17
- container.appendChild(header);
18
-
19
- this.textArea = document.createElement('textarea');
20
- this.textArea.id = 'query_log_text';
21
- this.textArea.readOnly = true;
22
- container.appendChild(this.textArea);
23
-
24
- rootEl.appendChild(container);
25
- }
26
-
27
- handleQueryRun(query: Query) {
28
- this.textArea.value += `${query.sql}\n`;
29
- }
30
- }
31
-
32
- export function initSqlLogView(
33
- rootEl: HTMLDivElement,
34
- queryRunner: QueryRunner
35
- ) {
36
- return new SqlLogView(rootEl, queryRunner);
37
- }
@@ -1,10 +0,0 @@
1
- #sql_log_container {
2
- flex-grow: 1;
3
- display: flex;
4
- flex-direction: column;
5
- }
6
-
7
- #query_log_text {
8
- resize: none;
9
- height: 100%;
10
- }