@h_domi/domi-indexed-sqlite 1.0.0
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/README.md +215 -0
- package/dist/index.d.mts +121 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +381 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +343 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# domi-indexed-sqlite
|
|
2
|
+
|
|
3
|
+
Nuxt 3 및 Vue 3 환경에서 `@sqlite.org/sqlite-wasm`을 더 쉽고 간편하게 사용할 수 있도록 만들어진 TypeScript 기반 SQLite 래퍼 패키지입니다.
|
|
4
|
+
|
|
5
|
+
이 라이브러리는 브라우저 보안 헤더(COOP/COEP) 설정이 없어도 브라우저 새로고침 시 데이터가 유지될 수 있도록 **IndexedDB를 기본 백엔드로 탑재**하고 있으며, 고성능 최적화가 필요할 시 손쉽게 **OPFS(Origin Private File System)**나 **Memory 모드**로 스위칭할 수 있습니다.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 주요 특징
|
|
10
|
+
|
|
11
|
+
- **IndexedDB 기반 영구 저장 (기본값)**: 브라우저 보안 헤더 설정 없이 설치 후 즉시 사용 가능합니다. 내부적으로 인메모리 SQLite를 가동하고 변경 사항을 IndexedDB로 자동 디바운싱 백업합니다.
|
|
12
|
+
- **OPFS 지원 (선택사항)**: 대용량 데이터베이스나 높은 읽기/쓰기 성능이 필요한 경우 브라우저 파일 시스템(OPFS)을 활용합니다.
|
|
13
|
+
- **Promise 기반 심플 API**: C 스타일의 투박한 API 대신 `db.query()` 및 `db.execute()` 형식의 단순화된 Promises API를 제공합니다.
|
|
14
|
+
- **TypeScript 완벽 지원**: 결과 행(Rows)에 대한 제네릭 타입 캐스팅을 완벽히 지원합니다.
|
|
15
|
+
- **CDN 폴백 탑재**: 복잡한 WASM 자산(Assets) 번들러 설정 없이 작동하도록 CDN 폴백 경로가 기본 내장되어 있습니다.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 설치 방법
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install domi-indexed-sqlite
|
|
23
|
+
# 또는
|
|
24
|
+
yarn add domi-indexed-sqlite
|
|
25
|
+
# 또는
|
|
26
|
+
pnpm add domi-indexed-sqlite
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 스토리지 타입 옵션 (`storageType`)
|
|
32
|
+
|
|
33
|
+
| 모드 | 설정값 | 영구저장 여부 | 보안헤더 요구 | 특징 |
|
|
34
|
+
| :--- | :--- | :---: | :---: | :--- |
|
|
35
|
+
| **IndexedDB (기본)** | `'indexeddb'` | **O** | **X** | 일반 호스팅(Github Pages, Netlify 등)에서 바로 영구 저장을 할 때 최적. |
|
|
36
|
+
| **인메모리** | `'memory'` | **X** | **X** | 새로고침 시 초기화됨. 임시 세션이나 유닛 테스트용으로 적합. |
|
|
37
|
+
| **OPFS** | `'opfs'` | **O** | **O** | 대용량 DB(10MB 이상) 처리 시 가장 빠른 속도 보장. (보안 헤더 설정 필요) |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 사용 방법
|
|
42
|
+
|
|
43
|
+
### 1. 데이터베이스 초기화 및 기본 쿼리
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { initEasySqlite } from 'domi-indexed-sqlite';
|
|
47
|
+
|
|
48
|
+
// DB 초기화 (기본적으로 IndexedDB 스토리지 활성화)
|
|
49
|
+
const db = await initEasySqlite({
|
|
50
|
+
dbName: 'my_app_database',
|
|
51
|
+
debug: true // 디버그 로그 활성화
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// 테이블 생성
|
|
55
|
+
await db.execute(`
|
|
56
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
57
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
58
|
+
name TEXT NOT NULL,
|
|
59
|
+
email TEXT UNIQUE,
|
|
60
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
61
|
+
)
|
|
62
|
+
`);
|
|
63
|
+
|
|
64
|
+
// 데이터 추가 (바인딩 파라미터 지원)
|
|
65
|
+
const insertResult = await db.execute(
|
|
66
|
+
'INSERT INTO users (name, email) VALUES (?, ?)',
|
|
67
|
+
['홍길동', 'gildong@example.com']
|
|
68
|
+
);
|
|
69
|
+
console.log('추가된 행 개수:', insertResult.rowsAffected);
|
|
70
|
+
|
|
71
|
+
// 데이터 조회 (TypeScript 제네릭 지원)
|
|
72
|
+
interface User {
|
|
73
|
+
id: number;
|
|
74
|
+
name: string;
|
|
75
|
+
email: string;
|
|
76
|
+
created_at: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const users = await db.query<User>('SELECT * FROM users ORDER BY id DESC');
|
|
80
|
+
console.log('사용자 목록:', users);
|
|
81
|
+
|
|
82
|
+
// 트랜잭션 처리
|
|
83
|
+
await db.transaction(async (tx) => {
|
|
84
|
+
await tx.execute('INSERT INTO users (name, email) VALUES (?, ?)', ['김철수', 'chulsoo@example.com']);
|
|
85
|
+
await tx.execute('INSERT INTO users (name, email) VALUES (?, ?)', ['이영희', 'younghee@example.com']);
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### 2. Vue 3 및 Nuxt 3 연동 가이드
|
|
92
|
+
|
|
93
|
+
#### A. Composable 패턴 구현 예시 (`composables/useSQLite.ts`)
|
|
94
|
+
Vue 3 환경에서는 애플리케이션 전역에서 싱글톤으로 DB 인스턴스를 유지하고, UI 반응형 연동을 쉽게 하기 위해 컴포저블 형태로 감싸서 제공하는 것이 좋습니다.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// composables/useSQLite.ts
|
|
98
|
+
import { ref } from 'vue';
|
|
99
|
+
import { initEasySqlite, EasySqlite } from 'domi-indexed-sqlite';
|
|
100
|
+
|
|
101
|
+
const dbInstance = ref<EasySqlite | null>(null);
|
|
102
|
+
const isReady = ref(false);
|
|
103
|
+
|
|
104
|
+
export function useSQLite() {
|
|
105
|
+
const initDb = async () => {
|
|
106
|
+
if (dbInstance.value) return dbInstance.value;
|
|
107
|
+
|
|
108
|
+
const db = await initEasySqlite({
|
|
109
|
+
dbName: 'user_local_db',
|
|
110
|
+
storageType: 'indexeddb' // 필요 시 'opfs'로 변경 가능
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
dbInstance.value = db;
|
|
114
|
+
isReady.value = true;
|
|
115
|
+
return db;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// 실시간 반응형 데이터 구독 헬퍼 (Live Query)
|
|
119
|
+
const useLiveQuery = <T = any>(sql: string, params: any[] = []) => {
|
|
120
|
+
const data = ref<T[]>([]);
|
|
121
|
+
const error = ref<any>(null);
|
|
122
|
+
const loading = ref(true);
|
|
123
|
+
|
|
124
|
+
const refresh = async () => {
|
|
125
|
+
if (!dbInstance.value) return;
|
|
126
|
+
loading.value = true;
|
|
127
|
+
try {
|
|
128
|
+
data.value = await dbInstance.value.query<T>(sql, params);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
error.value = err;
|
|
131
|
+
} finally {
|
|
132
|
+
loading.value = false;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// 초기 로딩
|
|
137
|
+
initDb().then(refresh);
|
|
138
|
+
|
|
139
|
+
return { data, error, loading, refresh };
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
initDb,
|
|
144
|
+
isReady,
|
|
145
|
+
useLiveQuery,
|
|
146
|
+
db: dbInstance
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### B. Vue 컴포넌트에서 사용 (`components/UserList.vue`)
|
|
152
|
+
|
|
153
|
+
```vue
|
|
154
|
+
<template>
|
|
155
|
+
<div>
|
|
156
|
+
<h2>로컬 사용자 목록</h2>
|
|
157
|
+
<div v-if="loading">DB 로딩 중...</div>
|
|
158
|
+
<ul v-else>
|
|
159
|
+
<li v-for="user in users" :key="user.id">
|
|
160
|
+
{{ user.name }} ({{ user.email }})
|
|
161
|
+
</li>
|
|
162
|
+
</ul>
|
|
163
|
+
|
|
164
|
+
<input v-model="newName" placeholder="이름 입력" />
|
|
165
|
+
<input v-model="newEmail" placeholder="이메일 입력" />
|
|
166
|
+
<button @click="addUser">추가</button>
|
|
167
|
+
</div>
|
|
168
|
+
</template>
|
|
169
|
+
|
|
170
|
+
<script setup lang="ts">
|
|
171
|
+
import { ref } from 'vue';
|
|
172
|
+
import { useSQLite } from '@/composables/useSQLite';
|
|
173
|
+
|
|
174
|
+
const { useLiveQuery, db } = useSQLite();
|
|
175
|
+
const { data: users, loading, refresh } = useLiveQuery('SELECT * FROM users');
|
|
176
|
+
|
|
177
|
+
const newName = ref('');
|
|
178
|
+
const newEmail = ref('');
|
|
179
|
+
|
|
180
|
+
const addUser = async () => {
|
|
181
|
+
if (!db.value) return;
|
|
182
|
+
await db.value.execute(
|
|
183
|
+
'INSERT INTO users (name, email) VALUES (?, ?)',
|
|
184
|
+
[newName.value, newEmail.value]
|
|
185
|
+
);
|
|
186
|
+
newName.value = '';
|
|
187
|
+
newEmail.value = '';
|
|
188
|
+
// 데이터 추가 후 리프레시하여 화면 갱신
|
|
189
|
+
refresh();
|
|
190
|
+
};
|
|
191
|
+
</script>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### 3. Vite / Nuxt 빌드 시 주의 사항
|
|
197
|
+
|
|
198
|
+
`@sqlite.org/sqlite-wasm`은 번들링 시 WASM 파일 및 Worker 의존성 처리가 필요합니다.
|
|
199
|
+
Vite 환경에서 원활한 빌드를 위해 라이브러리 사용자의 `vite.config.ts` 혹은 `nuxt.config.ts`에 아래 설정을 추가하는 것이 좋습니다.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// vite.config.ts 예시
|
|
203
|
+
export default defineConfig({
|
|
204
|
+
optimizeDeps: {
|
|
205
|
+
// Vite가 라이브러리 로드를 제대로 스캔하도록 종속성 최적화에서 예외 처리
|
|
206
|
+
exclude: ['@sqlite.org/sqlite-wasm']
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 라이센스
|
|
214
|
+
|
|
215
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
type StorageType = 'indexeddb' | 'memory' | 'opfs';
|
|
2
|
+
interface EasySqliteConfig {
|
|
3
|
+
/**
|
|
4
|
+
* 데이터베이스 이름입니다. OPFS 및 IndexedDB의 경우 파일 또는 키의 고유 이름으로 사용됩니다.
|
|
5
|
+
*/
|
|
6
|
+
dbName: string;
|
|
7
|
+
/**
|
|
8
|
+
* 데이터베이스를 저장할 스토리지 엔진 유형입니다.
|
|
9
|
+
* - 'indexeddb': DB 파일을 브라우저의 IndexedDB에 유지합니다. 특별한 COOP/COEP HTTP 보안 헤더 설정 없이도 안전하게 영구 저장이 가능합니다. (기본값)
|
|
10
|
+
* - 'memory': 메모리 상에만 존재하는 임시 데이터베이스입니다. 테스트나 일시적인 세션 작업에 적합하며, 페이지 새로고침 시 데이터가 휘발됩니다.
|
|
11
|
+
* - 'opfs': 브라우저의 고성능 파일 시스템인 Origin Private File System(OPFS)에 영구 저장합니다. 대량 데이터 처리에 적합하나, SharedArrayBuffer 보안 규격에 따른 COOP/COEP HTTP 헤더 설정이 필수적입니다.
|
|
12
|
+
*
|
|
13
|
+
* @default 'indexeddb'
|
|
14
|
+
*/
|
|
15
|
+
storageType?: StorageType;
|
|
16
|
+
/**
|
|
17
|
+
* sqlite3.wasm 바이너리 파일의 커스텀 URL 경로입니다 (선택사항).
|
|
18
|
+
* 지정하지 않으면 기본적으로 jsDelivr/unpkg 등의 CDN 경로로 자동 폴백(Fallback)됩니다.
|
|
19
|
+
*/
|
|
20
|
+
wasmUri?: string;
|
|
21
|
+
/**
|
|
22
|
+
* sqlite3-opfs-async-proxy.js 워커 파일의 커스텀 URL 경로입니다 (선택사항).
|
|
23
|
+
* WASM 파일을 로컬(self-host)로 서빙하며 OPFS 모드를 활성화할 경우 필수로 요구될 수 있습니다.
|
|
24
|
+
*/
|
|
25
|
+
workerUri?: string;
|
|
26
|
+
/**
|
|
27
|
+
* 개발자 콘솔에 디버그 로그를 출력할지 여부입니다.
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
debug?: boolean;
|
|
31
|
+
}
|
|
32
|
+
interface QueryResult {
|
|
33
|
+
/**
|
|
34
|
+
* 쿼리 실행 결과로 반환된 데이터 행(row) 배열입니다. 각 행은 컬럼명을 키로 하는 키-값 형태의 객체 구조입니다.
|
|
35
|
+
*/
|
|
36
|
+
rows: any[];
|
|
37
|
+
/**
|
|
38
|
+
* 쿼리 결과에 포함된 전체 컬럼명(열 이름)들의 문자열 배열입니다.
|
|
39
|
+
*/
|
|
40
|
+
columns: string[];
|
|
41
|
+
/**
|
|
42
|
+
* 해당 쿼리(예: INSERT, UPDATE, DELETE)로 인해 영향을 받거나 변경된 행의 개수입니다.
|
|
43
|
+
*/
|
|
44
|
+
rowsAffected: number;
|
|
45
|
+
}
|
|
46
|
+
interface EasySqliteInstance {
|
|
47
|
+
/**
|
|
48
|
+
* 지정한 SELECT SQL 쿼리를 실행하고 결과를 구조화된 객체 배열 형태로 반환합니다. (TypeScript 제네릭 지정 가능)
|
|
49
|
+
*/
|
|
50
|
+
query<T = any>(sql: string, params?: any[]): Promise<T[]>;
|
|
51
|
+
/**
|
|
52
|
+
* 데이터를 생성, 수정, 삭제하는 쿼리(INSERT, UPDATE, DELETE, CREATE TABLE 등)를 실행하고 쿼리 실행 세부 정보(영향받은 행의 개수 등)를 반환합니다.
|
|
53
|
+
*/
|
|
54
|
+
execute(sql: string, params?: any[]): Promise<QueryResult>;
|
|
55
|
+
/**
|
|
56
|
+
* 여러 개의 데이터베이스 쿼리를 단일 트랜잭션 단위로 묶어서 실행합니다.
|
|
57
|
+
* 콜백 함수 수행 중 에러 발생 시 변경 사항이 자동으로 롤백(Rollback)되며, 성공 시 커밋(Commit)됩니다.
|
|
58
|
+
*/
|
|
59
|
+
transaction<T>(cb: (tx: Omit<EasySqliteInstance, 'transaction' | 'close'>) => Promise<T>): Promise<T>;
|
|
60
|
+
/**
|
|
61
|
+
* 활성화되어 있는 데이터베이스 커넥션을 정상적으로 닫습니다.
|
|
62
|
+
* IndexedDB 모드일 경우 대기 중인 모든 미저장 데이터가 IndexedDB로 강제 동기화된 후 종료됩니다.
|
|
63
|
+
*/
|
|
64
|
+
close(): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 로컬 메인 스레드 데이터베이스(IndexedDB / Memory 모드) 및
|
|
69
|
+
* 백그라운드 워커 스레드 데이터베이스(OPFS 모드)를 관리하는 EasySqlite 구현체입니다.
|
|
70
|
+
*/
|
|
71
|
+
declare class EasySqlite implements EasySqliteInstance {
|
|
72
|
+
private config;
|
|
73
|
+
private storageType;
|
|
74
|
+
private db;
|
|
75
|
+
private sqlite3;
|
|
76
|
+
private promiser;
|
|
77
|
+
private opfsDbId;
|
|
78
|
+
private saveDebounced;
|
|
79
|
+
constructor(config: EasySqliteConfig);
|
|
80
|
+
private log;
|
|
81
|
+
private debounce;
|
|
82
|
+
/**
|
|
83
|
+
* 설정된 스토리지 타입에 기반하여 SQLite WASM 연동을 초기화합니다.
|
|
84
|
+
*/
|
|
85
|
+
init(): Promise<this>;
|
|
86
|
+
/**
|
|
87
|
+
* 로컬 메인 스레드 기반의 SQLite 인스턴스(Memory 또는 IndexedDB)를 초기화합니다.
|
|
88
|
+
*/
|
|
89
|
+
private initLocal;
|
|
90
|
+
/**
|
|
91
|
+
* Web Worker 및 Promiser API를 사용하여 OPFS 기반 SQLite 인스턴스를 초기화합니다.
|
|
92
|
+
*/
|
|
93
|
+
private initOpfs;
|
|
94
|
+
/**
|
|
95
|
+
* 현재 백그라운드 메모리의 SQLite 상태를 IndexedDB에 비동기로 백업(저장)합니다.
|
|
96
|
+
*/
|
|
97
|
+
private saveToIDB;
|
|
98
|
+
/**
|
|
99
|
+
* SQL 쿼리를 실행하여 결과 데이터 행(Row) 객체 배열을 반환합니다.
|
|
100
|
+
*/
|
|
101
|
+
query<T = any>(sql: string, params?: any[]): Promise<T[]>;
|
|
102
|
+
/**
|
|
103
|
+
* SQL 문을 실행하고 결과 행(rows), 열 정의(columns), 영향받은 행 개수(rowsAffected) 등의 상세 상세 정보를 반환합니다.
|
|
104
|
+
*/
|
|
105
|
+
execute(sql: string, params?: any[]): Promise<QueryResult>;
|
|
106
|
+
/**
|
|
107
|
+
* 단일 트랜잭션 내부에서 여러 데이터베이스 작업을 처리합니다.
|
|
108
|
+
*/
|
|
109
|
+
transaction<T>(cb: (tx: Omit<EasySqliteInstance, 'transaction' | 'close'>) => Promise<T>): Promise<T>;
|
|
110
|
+
/**
|
|
111
|
+
* 활성화된 데이터베이스 커넥션을 안전하게 닫습니다.
|
|
112
|
+
*/
|
|
113
|
+
close(): Promise<void>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* EasySqlite 데이터베이스 인스턴스를 즉시 생성하고 연동 초기화하는 편리한 팩토리 헬퍼 함수입니다.
|
|
117
|
+
* 중복 호출 시 이미 진행 중이거나 완료된 초기화 프로미스를 캐시하여 반환함으로써 중복 인스턴스 생성을 차단합니다.
|
|
118
|
+
*/
|
|
119
|
+
declare function initEasySqlite(config: EasySqliteConfig): Promise<EasySqlite>;
|
|
120
|
+
|
|
121
|
+
export { EasySqlite, initEasySqlite };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
type StorageType = 'indexeddb' | 'memory' | 'opfs';
|
|
2
|
+
interface EasySqliteConfig {
|
|
3
|
+
/**
|
|
4
|
+
* 데이터베이스 이름입니다. OPFS 및 IndexedDB의 경우 파일 또는 키의 고유 이름으로 사용됩니다.
|
|
5
|
+
*/
|
|
6
|
+
dbName: string;
|
|
7
|
+
/**
|
|
8
|
+
* 데이터베이스를 저장할 스토리지 엔진 유형입니다.
|
|
9
|
+
* - 'indexeddb': DB 파일을 브라우저의 IndexedDB에 유지합니다. 특별한 COOP/COEP HTTP 보안 헤더 설정 없이도 안전하게 영구 저장이 가능합니다. (기본값)
|
|
10
|
+
* - 'memory': 메모리 상에만 존재하는 임시 데이터베이스입니다. 테스트나 일시적인 세션 작업에 적합하며, 페이지 새로고침 시 데이터가 휘발됩니다.
|
|
11
|
+
* - 'opfs': 브라우저의 고성능 파일 시스템인 Origin Private File System(OPFS)에 영구 저장합니다. 대량 데이터 처리에 적합하나, SharedArrayBuffer 보안 규격에 따른 COOP/COEP HTTP 헤더 설정이 필수적입니다.
|
|
12
|
+
*
|
|
13
|
+
* @default 'indexeddb'
|
|
14
|
+
*/
|
|
15
|
+
storageType?: StorageType;
|
|
16
|
+
/**
|
|
17
|
+
* sqlite3.wasm 바이너리 파일의 커스텀 URL 경로입니다 (선택사항).
|
|
18
|
+
* 지정하지 않으면 기본적으로 jsDelivr/unpkg 등의 CDN 경로로 자동 폴백(Fallback)됩니다.
|
|
19
|
+
*/
|
|
20
|
+
wasmUri?: string;
|
|
21
|
+
/**
|
|
22
|
+
* sqlite3-opfs-async-proxy.js 워커 파일의 커스텀 URL 경로입니다 (선택사항).
|
|
23
|
+
* WASM 파일을 로컬(self-host)로 서빙하며 OPFS 모드를 활성화할 경우 필수로 요구될 수 있습니다.
|
|
24
|
+
*/
|
|
25
|
+
workerUri?: string;
|
|
26
|
+
/**
|
|
27
|
+
* 개발자 콘솔에 디버그 로그를 출력할지 여부입니다.
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
debug?: boolean;
|
|
31
|
+
}
|
|
32
|
+
interface QueryResult {
|
|
33
|
+
/**
|
|
34
|
+
* 쿼리 실행 결과로 반환된 데이터 행(row) 배열입니다. 각 행은 컬럼명을 키로 하는 키-값 형태의 객체 구조입니다.
|
|
35
|
+
*/
|
|
36
|
+
rows: any[];
|
|
37
|
+
/**
|
|
38
|
+
* 쿼리 결과에 포함된 전체 컬럼명(열 이름)들의 문자열 배열입니다.
|
|
39
|
+
*/
|
|
40
|
+
columns: string[];
|
|
41
|
+
/**
|
|
42
|
+
* 해당 쿼리(예: INSERT, UPDATE, DELETE)로 인해 영향을 받거나 변경된 행의 개수입니다.
|
|
43
|
+
*/
|
|
44
|
+
rowsAffected: number;
|
|
45
|
+
}
|
|
46
|
+
interface EasySqliteInstance {
|
|
47
|
+
/**
|
|
48
|
+
* 지정한 SELECT SQL 쿼리를 실행하고 결과를 구조화된 객체 배열 형태로 반환합니다. (TypeScript 제네릭 지정 가능)
|
|
49
|
+
*/
|
|
50
|
+
query<T = any>(sql: string, params?: any[]): Promise<T[]>;
|
|
51
|
+
/**
|
|
52
|
+
* 데이터를 생성, 수정, 삭제하는 쿼리(INSERT, UPDATE, DELETE, CREATE TABLE 등)를 실행하고 쿼리 실행 세부 정보(영향받은 행의 개수 등)를 반환합니다.
|
|
53
|
+
*/
|
|
54
|
+
execute(sql: string, params?: any[]): Promise<QueryResult>;
|
|
55
|
+
/**
|
|
56
|
+
* 여러 개의 데이터베이스 쿼리를 단일 트랜잭션 단위로 묶어서 실행합니다.
|
|
57
|
+
* 콜백 함수 수행 중 에러 발생 시 변경 사항이 자동으로 롤백(Rollback)되며, 성공 시 커밋(Commit)됩니다.
|
|
58
|
+
*/
|
|
59
|
+
transaction<T>(cb: (tx: Omit<EasySqliteInstance, 'transaction' | 'close'>) => Promise<T>): Promise<T>;
|
|
60
|
+
/**
|
|
61
|
+
* 활성화되어 있는 데이터베이스 커넥션을 정상적으로 닫습니다.
|
|
62
|
+
* IndexedDB 모드일 경우 대기 중인 모든 미저장 데이터가 IndexedDB로 강제 동기화된 후 종료됩니다.
|
|
63
|
+
*/
|
|
64
|
+
close(): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 로컬 메인 스레드 데이터베이스(IndexedDB / Memory 모드) 및
|
|
69
|
+
* 백그라운드 워커 스레드 데이터베이스(OPFS 모드)를 관리하는 EasySqlite 구현체입니다.
|
|
70
|
+
*/
|
|
71
|
+
declare class EasySqlite implements EasySqliteInstance {
|
|
72
|
+
private config;
|
|
73
|
+
private storageType;
|
|
74
|
+
private db;
|
|
75
|
+
private sqlite3;
|
|
76
|
+
private promiser;
|
|
77
|
+
private opfsDbId;
|
|
78
|
+
private saveDebounced;
|
|
79
|
+
constructor(config: EasySqliteConfig);
|
|
80
|
+
private log;
|
|
81
|
+
private debounce;
|
|
82
|
+
/**
|
|
83
|
+
* 설정된 스토리지 타입에 기반하여 SQLite WASM 연동을 초기화합니다.
|
|
84
|
+
*/
|
|
85
|
+
init(): Promise<this>;
|
|
86
|
+
/**
|
|
87
|
+
* 로컬 메인 스레드 기반의 SQLite 인스턴스(Memory 또는 IndexedDB)를 초기화합니다.
|
|
88
|
+
*/
|
|
89
|
+
private initLocal;
|
|
90
|
+
/**
|
|
91
|
+
* Web Worker 및 Promiser API를 사용하여 OPFS 기반 SQLite 인스턴스를 초기화합니다.
|
|
92
|
+
*/
|
|
93
|
+
private initOpfs;
|
|
94
|
+
/**
|
|
95
|
+
* 현재 백그라운드 메모리의 SQLite 상태를 IndexedDB에 비동기로 백업(저장)합니다.
|
|
96
|
+
*/
|
|
97
|
+
private saveToIDB;
|
|
98
|
+
/**
|
|
99
|
+
* SQL 쿼리를 실행하여 결과 데이터 행(Row) 객체 배열을 반환합니다.
|
|
100
|
+
*/
|
|
101
|
+
query<T = any>(sql: string, params?: any[]): Promise<T[]>;
|
|
102
|
+
/**
|
|
103
|
+
* SQL 문을 실행하고 결과 행(rows), 열 정의(columns), 영향받은 행 개수(rowsAffected) 등의 상세 상세 정보를 반환합니다.
|
|
104
|
+
*/
|
|
105
|
+
execute(sql: string, params?: any[]): Promise<QueryResult>;
|
|
106
|
+
/**
|
|
107
|
+
* 단일 트랜잭션 내부에서 여러 데이터베이스 작업을 처리합니다.
|
|
108
|
+
*/
|
|
109
|
+
transaction<T>(cb: (tx: Omit<EasySqliteInstance, 'transaction' | 'close'>) => Promise<T>): Promise<T>;
|
|
110
|
+
/**
|
|
111
|
+
* 활성화된 데이터베이스 커넥션을 안전하게 닫습니다.
|
|
112
|
+
*/
|
|
113
|
+
close(): Promise<void>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* EasySqlite 데이터베이스 인스턴스를 즉시 생성하고 연동 초기화하는 편리한 팩토리 헬퍼 함수입니다.
|
|
117
|
+
* 중복 호출 시 이미 진행 중이거나 완료된 초기화 프로미스를 캐시하여 반환함으로써 중복 인스턴스 생성을 차단합니다.
|
|
118
|
+
*/
|
|
119
|
+
declare function initEasySqlite(config: EasySqliteConfig): Promise<EasySqlite>;
|
|
120
|
+
|
|
121
|
+
export { EasySqlite, initEasySqlite };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
EasySqlite: () => EasySqlite,
|
|
34
|
+
initEasySqlite: () => initEasySqlite
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/idb-helper.ts
|
|
39
|
+
var DB_NAME = "domi_sqlite_store";
|
|
40
|
+
var STORE_NAME = "databases";
|
|
41
|
+
function openIDB() {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const request = indexedDB.open(DB_NAME, 1);
|
|
44
|
+
request.onupgradeneeded = () => {
|
|
45
|
+
const db = request.result;
|
|
46
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
47
|
+
db.createObjectStore(STORE_NAME);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
request.onsuccess = () => {
|
|
51
|
+
resolve(request.result);
|
|
52
|
+
};
|
|
53
|
+
request.onerror = () => {
|
|
54
|
+
reject(request.error);
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async function getDatabaseFromIndexedDB(dbName) {
|
|
59
|
+
try {
|
|
60
|
+
const db = await openIDB();
|
|
61
|
+
return await new Promise((resolve, reject) => {
|
|
62
|
+
const transaction = db.transaction(STORE_NAME, "readonly");
|
|
63
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
64
|
+
const getReq = store.get(dbName);
|
|
65
|
+
getReq.onsuccess = () => {
|
|
66
|
+
resolve(getReq.result || null);
|
|
67
|
+
};
|
|
68
|
+
getReq.onerror = () => {
|
|
69
|
+
reject(getReq.error);
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error("[domi-sqlite] IndexedDB\uC5D0\uC11C DB\uB97C \uB85C\uB4DC\uD558\uB294\uB370 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4:", error);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function saveDatabaseToIndexedDB(dbName, data) {
|
|
78
|
+
try {
|
|
79
|
+
const db = await openIDB();
|
|
80
|
+
await new Promise((resolve, reject) => {
|
|
81
|
+
const transaction = db.transaction(STORE_NAME, "readwrite");
|
|
82
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
83
|
+
const putReq = store.put(data, dbName);
|
|
84
|
+
putReq.onsuccess = () => {
|
|
85
|
+
resolve();
|
|
86
|
+
};
|
|
87
|
+
putReq.onerror = () => {
|
|
88
|
+
reject(putReq.error);
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error("[domi-sqlite] IndexedDB\uC5D0 DB\uB97C \uBC31\uC5C5\uD558\uB294\uB370 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4:", error);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/index.ts
|
|
98
|
+
var DEFAULT_WASM_URI = "https://unpkg.com/@sqlite.org/sqlite-wasm@3.46.1-build5/sqlite-wasm/jswasm/sqlite3.wasm";
|
|
99
|
+
var DEFAULT_WORKER_URI = "https://unpkg.com/@sqlite.org/sqlite-wasm@3.46.1-build5/sqlite-wasm/jswasm/sqlite3-worker1.js";
|
|
100
|
+
var EasySqlite = class {
|
|
101
|
+
config;
|
|
102
|
+
storageType;
|
|
103
|
+
db = null;
|
|
104
|
+
// 메모리/IndexedDB 모드용 (oo1.DB 인스턴스)
|
|
105
|
+
sqlite3 = null;
|
|
106
|
+
// 메모리/IndexedDB 모드용 모듈 캐시
|
|
107
|
+
promiser = null;
|
|
108
|
+
// OPFS 모드용 Worker Promiser
|
|
109
|
+
opfsDbId = null;
|
|
110
|
+
// OPFS 모드용 DB 고유 식별자
|
|
111
|
+
saveDebounced;
|
|
112
|
+
constructor(config) {
|
|
113
|
+
this.config = {
|
|
114
|
+
dbName: config.dbName,
|
|
115
|
+
storageType: config.storageType || "indexeddb",
|
|
116
|
+
wasmUri: config.wasmUri || DEFAULT_WASM_URI,
|
|
117
|
+
workerUri: config.workerUri || DEFAULT_WORKER_URI,
|
|
118
|
+
debug: config.debug || false
|
|
119
|
+
};
|
|
120
|
+
this.storageType = this.config.storageType;
|
|
121
|
+
this.saveDebounced = this.debounce(this.saveToIDB.bind(this), 250);
|
|
122
|
+
}
|
|
123
|
+
log(...args) {
|
|
124
|
+
if (this.config.debug) {
|
|
125
|
+
console.log("[domi-sqlite]", ...args);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
debounce(func, wait) {
|
|
129
|
+
let timeout;
|
|
130
|
+
return (...args) => {
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
timeout = setTimeout(() => func(...args), wait);
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 설정된 스토리지 타입에 기반하여 SQLite WASM 연동을 초기화합니다.
|
|
137
|
+
*/
|
|
138
|
+
async init() {
|
|
139
|
+
if (typeof window === "undefined") {
|
|
140
|
+
this.log("\uC11C\uBC84 \uC0AC\uC774\uB4DC \uD658\uACBD \uAC10\uC9C0: init() \uC2E4\uD589\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4.");
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
if (this.storageType === "opfs") {
|
|
144
|
+
await this.initOpfs();
|
|
145
|
+
} else {
|
|
146
|
+
await this.initLocal();
|
|
147
|
+
}
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 로컬 메인 스레드 기반의 SQLite 인스턴스(Memory 또는 IndexedDB)를 초기화합니다.
|
|
152
|
+
*/
|
|
153
|
+
async initLocal() {
|
|
154
|
+
if (typeof window === "undefined") return;
|
|
155
|
+
this.log(`\uB85C\uCEEC SQLite \uCD08\uAE30\uD654 \uC911 (\uBAA8\uB4DC: ${this.storageType})...`);
|
|
156
|
+
const wasmUrl = this.config.wasmUri;
|
|
157
|
+
const { default: sqlite3InitModule } = await import("@sqlite.org/sqlite-wasm");
|
|
158
|
+
this.sqlite3 = await sqlite3InitModule({
|
|
159
|
+
locateFile: (file) => {
|
|
160
|
+
if (file === "sqlite3.wasm") {
|
|
161
|
+
return wasmUrl;
|
|
162
|
+
}
|
|
163
|
+
return file;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
const oo1 = this.sqlite3.oo1;
|
|
167
|
+
this.db = new oo1.DB();
|
|
168
|
+
if (this.storageType === "indexeddb") {
|
|
169
|
+
const savedData = await getDatabaseFromIndexedDB(this.config.dbName);
|
|
170
|
+
if (savedData && savedData.byteLength > 0) {
|
|
171
|
+
this.log(`IndexedDB\uC5D0\uC11C \uAE30\uC874 \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uBC1C\uACAC (${savedData.byteLength} \uBC14\uC774\uD2B8). \uBCF5\uC6D0(Deserializing)\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4...`);
|
|
172
|
+
try {
|
|
173
|
+
const capi = this.sqlite3.capi;
|
|
174
|
+
const wasm = this.sqlite3.wasm;
|
|
175
|
+
const p = capi.sqlite3_malloc(savedData.byteLength);
|
|
176
|
+
if (!p) {
|
|
177
|
+
throw new Error("WebAssembly \uBA54\uBAA8\uB9AC \uD560\uB2F9\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (sqlite3_malloc).");
|
|
178
|
+
}
|
|
179
|
+
wasm.heap8u().set(savedData, p);
|
|
180
|
+
const deserializeFlags = capi.SQLITE_DESERIALIZE_FREEONCLOSE | capi.SQLITE_DESERIALIZE_RESIZEABLE;
|
|
181
|
+
const rc = capi.sqlite3_deserialize(
|
|
182
|
+
this.db.pointer,
|
|
183
|
+
"main",
|
|
184
|
+
p,
|
|
185
|
+
savedData.byteLength,
|
|
186
|
+
savedData.byteLength,
|
|
187
|
+
deserializeFlags
|
|
188
|
+
);
|
|
189
|
+
this.db.checkRc(rc);
|
|
190
|
+
this.log("\uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uBCF5\uC6D0 \uC131\uACF5.");
|
|
191
|
+
} catch (err) {
|
|
192
|
+
console.error("[domi-sqlite] \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uBCF5\uC6D0 \uC2E4\uD328. \uC0C8 \uBE48 \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB85C \uC2DC\uC791\uD569\uB2C8\uB2E4.", err);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
this.log("IndexedDB\uC5D0 \uC800\uC7A5\uB41C \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uC0C8\uB85C \uCD08\uAE30\uD654\uD569\uB2C8\uB2E4.");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Web Worker 및 Promiser API를 사용하여 OPFS 기반 SQLite 인스턴스를 초기화합니다.
|
|
201
|
+
*/
|
|
202
|
+
async initOpfs() {
|
|
203
|
+
this.log("Web Worker\uB97C \uC0AC\uC6A9\uD558\uC5EC OPFS SQLite \uCD08\uAE30\uD654 \uC911...");
|
|
204
|
+
const { sqlite3Worker1Promiser } = await import("@sqlite.org/sqlite-wasm");
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
try {
|
|
207
|
+
const workerUrl = this.config.workerUri;
|
|
208
|
+
const blobCode = `importScripts(${JSON.stringify(workerUrl)});`;
|
|
209
|
+
const blob = new Blob([blobCode], { type: "application/javascript" });
|
|
210
|
+
const worker = new Worker(URL.createObjectURL(blob));
|
|
211
|
+
const promiserInstance = sqlite3Worker1Promiser({
|
|
212
|
+
worker: () => worker,
|
|
213
|
+
onready: async () => {
|
|
214
|
+
this.promiser = promiserInstance;
|
|
215
|
+
try {
|
|
216
|
+
this.log(`OPFS\uB97C \uD1B5\uD574 \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uD30C\uC77C "${this.config.dbName}"\uC744(\uB97C) \uC5EC\uB294 \uC911...`);
|
|
217
|
+
const openResult = await this.promiser("open", {
|
|
218
|
+
filename: this.config.dbName,
|
|
219
|
+
vfs: "opfs"
|
|
220
|
+
});
|
|
221
|
+
this.opfsDbId = openResult.dbId;
|
|
222
|
+
this.log("OPFS \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC624\uD508 \uC131\uACF5. DB ID:", this.opfsDbId);
|
|
223
|
+
resolve();
|
|
224
|
+
} catch (err) {
|
|
225
|
+
reject(err);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
onerror: (err) => {
|
|
229
|
+
reject(err);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
} catch (err) {
|
|
233
|
+
reject(err);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* 현재 백그라운드 메모리의 SQLite 상태를 IndexedDB에 비동기로 백업(저장)합니다.
|
|
239
|
+
*/
|
|
240
|
+
saveToIDB() {
|
|
241
|
+
if (this.storageType !== "indexeddb" || !this.db || !this.sqlite3) return;
|
|
242
|
+
try {
|
|
243
|
+
this.log("\uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB97C IndexedDB\uC5D0 \uBC31\uC5C5\uD558\uB294 \uC911...");
|
|
244
|
+
const exported = this.sqlite3.capi.sqlite3_js_db_export(this.db.pointer);
|
|
245
|
+
if (exported) {
|
|
246
|
+
saveDatabaseToIndexedDB(this.config.dbName, exported).then(() => this.log("IndexedDB \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uBC31\uC5C5 \uC644\uB8CC.")).catch((err) => console.error("[domi-sqlite] IndexedDB \uBC31\uC5C5 \uC911 \uC5D0\uB7EC \uBC1C\uC0DD:", err));
|
|
247
|
+
}
|
|
248
|
+
} catch (err) {
|
|
249
|
+
console.error("[domi-sqlite] \uBC31\uC5C5\uC6A9 \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC5D1\uC2A4\uD3EC\uD2B8 \uC2E4\uD328:", err);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* SQL 쿼리를 실행하여 결과 데이터 행(Row) 객체 배열을 반환합니다.
|
|
254
|
+
*/
|
|
255
|
+
async query(sql, params = []) {
|
|
256
|
+
const res = await this.execute(sql, params);
|
|
257
|
+
return res.rows;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* SQL 문을 실행하고 결과 행(rows), 열 정의(columns), 영향받은 행 개수(rowsAffected) 등의 상세 상세 정보를 반환합니다.
|
|
261
|
+
*/
|
|
262
|
+
async execute(sql, params = []) {
|
|
263
|
+
if (typeof window === "undefined") {
|
|
264
|
+
return { rows: [], columns: [], rowsAffected: 0 };
|
|
265
|
+
}
|
|
266
|
+
if (this.storageType === "opfs") {
|
|
267
|
+
if (!this.promiser || !this.opfsDbId) {
|
|
268
|
+
throw new Error("\uB370\uC774\uD130\uBCA0\uC774\uC2A4\uAC00 \uCD08\uAE30\uD654\uB418\uC9C0 \uC54A\uC558\uAC70\uB098 \uC774\uBBF8 \uB2EB\uD600\uC788\uC2B5\uB2C8\uB2E4.");
|
|
269
|
+
}
|
|
270
|
+
this.log("OPFS \uCFFC\uB9AC \uC2E4\uD589:", sql, params);
|
|
271
|
+
const res = await this.promiser("exec", {
|
|
272
|
+
dbId: this.opfsDbId,
|
|
273
|
+
sql,
|
|
274
|
+
bind: params
|
|
275
|
+
});
|
|
276
|
+
const rows = [];
|
|
277
|
+
const columns = res.result.columnNames || [];
|
|
278
|
+
if (res.result.row) {
|
|
279
|
+
const valuesList = res.result.values || [];
|
|
280
|
+
for (const vals of valuesList) {
|
|
281
|
+
const rowObj = {};
|
|
282
|
+
columns.forEach((col, idx) => {
|
|
283
|
+
rowObj[col] = vals[idx];
|
|
284
|
+
});
|
|
285
|
+
rows.push(rowObj);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
rows,
|
|
290
|
+
columns,
|
|
291
|
+
rowsAffected: res.result.changeCount || 0
|
|
292
|
+
};
|
|
293
|
+
} else {
|
|
294
|
+
if (!this.db) {
|
|
295
|
+
throw new Error("\uB370\uC774\uD130\uBCA0\uC774\uC2A4\uAC00 \uCD08\uAE30\uD654\uB418\uC9C0 \uC54A\uC558\uAC70\uB098 \uC774\uBBF8 \uB2EB\uD600\uC788\uC2B5\uB2C8\uB2E4.");
|
|
296
|
+
}
|
|
297
|
+
this.log("\uB85C\uCEEC SQLite \uCFFC\uB9AC \uC2E4\uD589:", sql, params);
|
|
298
|
+
const columns = [];
|
|
299
|
+
const rows = [];
|
|
300
|
+
this.db.exec({
|
|
301
|
+
sql,
|
|
302
|
+
bind: params,
|
|
303
|
+
rowMode: "object",
|
|
304
|
+
columnNames: columns,
|
|
305
|
+
callback: (row) => {
|
|
306
|
+
rows.push(row);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
const isWriteQuery = /^\s*(insert|update|delete|create|drop|alter|replace)/i.test(sql);
|
|
310
|
+
if (this.storageType === "indexeddb" && isWriteQuery) {
|
|
311
|
+
this.saveDebounced();
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
rows,
|
|
315
|
+
columns,
|
|
316
|
+
rowsAffected: this.db.changes()
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* 단일 트랜잭션 내부에서 여러 데이터베이스 작업을 처리합니다.
|
|
322
|
+
*/
|
|
323
|
+
async transaction(cb) {
|
|
324
|
+
await this.execute("BEGIN TRANSACTION");
|
|
325
|
+
try {
|
|
326
|
+
const result = await cb(this);
|
|
327
|
+
await this.execute("COMMIT");
|
|
328
|
+
return result;
|
|
329
|
+
} catch (err) {
|
|
330
|
+
await this.execute("ROLLBACK");
|
|
331
|
+
throw err;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* 활성화된 데이터베이스 커넥션을 안전하게 닫습니다.
|
|
336
|
+
*/
|
|
337
|
+
async close() {
|
|
338
|
+
if (this.storageType === "opfs") {
|
|
339
|
+
if (this.promiser && this.opfsDbId) {
|
|
340
|
+
this.log("OPFS \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uCEE4\uB125\uC158 \uC885\uB8CC \uC911...");
|
|
341
|
+
await this.promiser("close", { dbId: this.opfsDbId });
|
|
342
|
+
this.opfsDbId = null;
|
|
343
|
+
this.promiser = null;
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
if (this.db) {
|
|
347
|
+
this.log("\uB85C\uCEEC \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uCEE4\uB125\uC158 \uC885\uB8CC \uC911...");
|
|
348
|
+
if (this.storageType === "indexeddb") {
|
|
349
|
+
this.saveToIDB();
|
|
350
|
+
}
|
|
351
|
+
this.db.close();
|
|
352
|
+
this.db = null;
|
|
353
|
+
this.sqlite3 = null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
var initCache = /* @__PURE__ */ new Map();
|
|
359
|
+
function initEasySqlite(config) {
|
|
360
|
+
const cacheKey = `${config.storageType || "indexeddb"}:${config.dbName}`;
|
|
361
|
+
if (initCache.has(cacheKey)) {
|
|
362
|
+
return initCache.get(cacheKey);
|
|
363
|
+
}
|
|
364
|
+
const promise = (async () => {
|
|
365
|
+
try {
|
|
366
|
+
const instance = new EasySqlite(config);
|
|
367
|
+
return await instance.init();
|
|
368
|
+
} catch (err) {
|
|
369
|
+
initCache.delete(cacheKey);
|
|
370
|
+
throw err;
|
|
371
|
+
}
|
|
372
|
+
})();
|
|
373
|
+
initCache.set(cacheKey, promise);
|
|
374
|
+
return promise;
|
|
375
|
+
}
|
|
376
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
377
|
+
0 && (module.exports = {
|
|
378
|
+
EasySqlite,
|
|
379
|
+
initEasySqlite
|
|
380
|
+
});
|
|
381
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/idb-helper.ts"],"sourcesContent":["import { getDatabaseFromIndexedDB, saveDatabaseToIndexedDB } from './idb-helper.js';\nimport { EasySqliteConfig, EasySqliteInstance, QueryResult, StorageType } from './types.js';\n\n// 번들러 구성 없이도 간편하게 연동할 수 있도록 제공하는 기본 CDN(unpkg) 경로 설정\nconst DEFAULT_WASM_URI = 'https://unpkg.com/@sqlite.org/sqlite-wasm@3.46.1-build5/sqlite-wasm/jswasm/sqlite3.wasm';\nconst DEFAULT_WORKER_URI = 'https://unpkg.com/@sqlite.org/sqlite-wasm@3.46.1-build5/sqlite-wasm/jswasm/sqlite3-worker1.js';\n\n/**\n * 로컬 메인 스레드 데이터베이스(IndexedDB / Memory 모드) 및\n * 백그라운드 워커 스레드 데이터베이스(OPFS 모드)를 관리하는 EasySqlite 구현체입니다.\n */\nexport class EasySqlite implements EasySqliteInstance {\n private config: Required<EasySqliteConfig>;\n private storageType: StorageType;\n private db: any = null; // 메모리/IndexedDB 모드용 (oo1.DB 인스턴스)\n private sqlite3: any = null; // 메모리/IndexedDB 모드용 모듈 캐시\n private promiser: any = null; // OPFS 모드용 Worker Promiser\n private opfsDbId: string | null = null; // OPFS 모드용 DB 고유 식별자\n private saveDebounced: () => void;\n\n constructor(config: EasySqliteConfig) {\n this.config = {\n dbName: config.dbName,\n storageType: config.storageType || 'indexeddb',\n wasmUri: config.wasmUri || DEFAULT_WASM_URI,\n workerUri: config.workerUri || DEFAULT_WORKER_URI,\n debug: config.debug || false,\n };\n this.storageType = this.config.storageType;\n\n // 연속적인 쿼리 요청이 올 때 IndexedDB에 과도하게 디스크 쓰기가 발생하는 것을 방지하기 위해 디바운싱 처리 (250ms)\n this.saveDebounced = this.debounce(this.saveToIDB.bind(this), 250);\n }\n\n private log(...args: any[]) {\n if (this.config.debug) {\n console.log('[domi-sqlite]', ...args);\n }\n }\n\n private debounce(func: (...args: any[]) => void, wait: number) {\n let timeout: any;\n return (...args: any[]) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => func(...args), wait);\n };\n }\n\n /**\n * 설정된 스토리지 타입에 기반하여 SQLite WASM 연동을 초기화합니다.\n */\n async init(): Promise<this> {\n // 서버 사이드 렌더링(SSR) 환경인 경우 초기화를 수행하지 않고 즉시 스킵합니다.\n if (typeof window === 'undefined') {\n this.log('서버 사이드 환경 감지: init() 실행을 건너뜁니다.');\n return this;\n }\n\n if (this.storageType === 'opfs') {\n await this.initOpfs();\n } else {\n await this.initLocal();\n }\n return this;\n }\n\n /**\n * 로컬 메인 스레드 기반의 SQLite 인스턴스(Memory 또는 IndexedDB)를 초기화합니다.\n */\n private async initLocal() {\n if (typeof window === 'undefined') return;\n this.log(`로컬 SQLite 초기화 중 (모드: ${this.storageType})...`);\n\n const wasmUrl = this.config.wasmUri;\n\n // 서버 사이드 컴파일 크래시를 방지하기 위해 클라이언트 런타임에 동적으로 모듈을 로드합니다.\n const { default: sqlite3InitModule } = await import('@sqlite.org/sqlite-wasm');\n\n this.sqlite3 = await sqlite3InitModule({\n locateFile: (file: string) => {\n if (file === 'sqlite3.wasm') {\n return wasmUrl;\n }\n return file;\n },\n });\n\n const oo1 = this.sqlite3.oo1;\n this.db = new oo1.DB();\n\n if (this.storageType === 'indexeddb') {\n const savedData = await getDatabaseFromIndexedDB(this.config.dbName);\n if (savedData && savedData.byteLength > 0) {\n this.log(`IndexedDB에서 기존 데이터베이스 발견 (${savedData.byteLength} 바이트). 복원(Deserializing)을 시작합니다...`);\n try {\n const capi = this.sqlite3.capi;\n const wasm = this.sqlite3.wasm;\n\n // SQLite의 sqlite3_realloc과 완벽히 호환되도록 capi.sqlite3_malloc을 통해 메모리를 명시적으로 할당합니다.\n const p = capi.sqlite3_malloc(savedData.byteLength);\n if (!p) {\n throw new Error('WebAssembly 메모리 할당에 실패했습니다 (sqlite3_malloc).');\n }\n\n // 할당된 WASM 메모리 영역에 기존 데이터베이스 바이너리를 복사합니다.\n wasm.heap8u().set(savedData, p);\n \n // 메모리 내 데이터베이스로 바이너리 값을 Deserialize(복원)합니다.\n // SQLITE_DESERIALIZE_FREEONCLOSE와 RESIZEABLE 조합은 버퍼가 sqlite3_malloc으로 할당된 경우에만 안전하게 동작합니다.\n const deserializeFlags = \n capi.SQLITE_DESERIALIZE_FREEONCLOSE | \n capi.SQLITE_DESERIALIZE_RESIZEABLE;\n\n const rc = capi.sqlite3_deserialize(\n this.db.pointer,\n 'main',\n p,\n savedData.byteLength,\n savedData.byteLength,\n deserializeFlags\n );\n\n this.db.checkRc(rc);\n this.log('데이터베이스 복원 성공.');\n } catch (err) {\n console.error('[domi-sqlite] 데이터베이스 복원 실패. 새 빈 데이터베이스로 시작합니다.', err);\n }\n } else {\n this.log('IndexedDB에 저장된 데이터베이스가 없습니다. 새로 초기화합니다.');\n }\n }\n }\n\n /**\n * Web Worker 및 Promiser API를 사용하여 OPFS 기반 SQLite 인스턴스를 초기화합니다.\n */\n private async initOpfs() {\n this.log('Web Worker를 사용하여 OPFS SQLite 초기화 중...');\n\n // 빌드 타임에 OPFS Worker와 메인 스레드 간 프로토콜을 설정하는 모듈을 다이내믹 임포트합니다.\n const { sqlite3Worker1Promiser } = await import('@sqlite.org/sqlite-wasm');\n\n return new Promise<void>((resolve, reject) => {\n try {\n const workerUrl = this.config.workerUri;\n // CDN 호스팅 시 발생할 수 있는 동일출처정책(Same-Origin Policy) 우회를 위한 Blob 프록시 워커 처리\n const blobCode = `importScripts(${JSON.stringify(workerUrl)});`;\n const blob = new Blob([blobCode], { type: 'application/javascript' });\n const worker = new Worker(URL.createObjectURL(blob));\n\n const promiserInstance = sqlite3Worker1Promiser({\n worker: () => worker,\n onready: async () => {\n this.promiser = promiserInstance;\n try {\n this.log(`OPFS를 통해 데이터베이스 파일 \"${this.config.dbName}\"을(를) 여는 중...`);\n const openResult = await this.promiser('open', {\n filename: this.config.dbName,\n vfs: 'opfs',\n });\n this.opfsDbId = openResult.dbId;\n this.log('OPFS 데이터베이스 오픈 성공. DB ID:', this.opfsDbId);\n resolve();\n } catch (err) {\n reject(err);\n }\n },\n onerror: (err: any) => {\n reject(err);\n },\n });\n } catch (err) {\n reject(err);\n }\n });\n }\n\n /**\n * 현재 백그라운드 메모리의 SQLite 상태를 IndexedDB에 비동기로 백업(저장)합니다.\n */\n private saveToIDB() {\n if (this.storageType !== 'indexeddb' || !this.db || !this.sqlite3) return;\n\n try {\n this.log('데이터베이스를 IndexedDB에 백업하는 중...');\n // 메모리에 로드된 SQLite 인스턴스 전체를 Uint8Array 바이너리로 내보냅니다.\n const exported = this.sqlite3.capi.sqlite3_js_db_export(this.db.pointer);\n if (exported) {\n saveDatabaseToIndexedDB(this.config.dbName, exported)\n .then(() => this.log('IndexedDB 데이터베이스 백업 완료.'))\n .catch((err) => console.error('[domi-sqlite] IndexedDB 백업 중 에러 발생:', err));\n }\n } catch (err) {\n console.error('[domi-sqlite] 백업용 데이터베이스 엑스포트 실패:', err);\n }\n }\n\n /**\n * SQL 쿼리를 실행하여 결과 데이터 행(Row) 객체 배열을 반환합니다.\n */\n async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {\n const res = await this.execute(sql, params);\n return res.rows as T[];\n }\n\n /**\n * SQL 문을 실행하고 결과 행(rows), 열 정의(columns), 영향받은 행 개수(rowsAffected) 등의 상세 상세 정보를 반환합니다.\n */\n async execute(sql: string, params: any[] = []): Promise<QueryResult> {\n // SSR 환경인 경우 쿼리 실행을 방지하고 빈 결과를 안전하게 반환합니다.\n if (typeof window === 'undefined') {\n return { rows: [], columns: [], rowsAffected: 0 };\n }\n\n if (this.storageType === 'opfs') {\n if (!this.promiser || !this.opfsDbId) {\n throw new Error('데이터베이스가 초기화되지 않았거나 이미 닫혀있습니다.');\n }\n\n this.log('OPFS 쿼리 실행:', sql, params);\n const res = await this.promiser('exec', {\n dbId: this.opfsDbId,\n sql,\n bind: params,\n });\n\n // Promiser API 출력 구조에 맞춰 결과를 재구성합니다.\n const rows: any[] = [];\n const columns = res.result.columnNames || [];\n\n if (res.result.row) {\n // 결과값을 순회하며 컬럼명과 매핑되는 행 객체 구조로 뱐환합니다.\n const valuesList = res.result.values || [];\n for (const vals of valuesList) {\n const rowObj: any = {};\n columns.forEach((col: string, idx: number) => {\n rowObj[col] = vals[idx];\n });\n rows.push(rowObj);\n }\n }\n\n return {\n rows,\n columns,\n rowsAffected: res.result.changeCount || 0,\n };\n } else {\n if (!this.db) {\n throw new Error('데이터베이스가 초기화되지 않았거나 이미 닫혀있습니다.');\n }\n\n this.log('로컬 SQLite 쿼리 실행:', sql, params);\n const columns: string[] = [];\n const rows: any[] = [];\n\n this.db.exec({\n sql,\n bind: params,\n rowMode: 'object',\n columnNames: columns,\n callback: (row: any) => {\n rows.push(row);\n },\n });\n\n // 데이터가 수정되거나 추가되는 쓰기 작업 쿼리(INSERT, UPDATE 등)가 발생한 경우 백업을 수행합니다.\n const isWriteQuery = /^\\s*(insert|update|delete|create|drop|alter|replace)/i.test(sql);\n if (this.storageType === 'indexeddb' && isWriteQuery) {\n this.saveDebounced();\n }\n\n return {\n rows,\n columns,\n rowsAffected: this.db.changes(),\n };\n }\n }\n\n /**\n * 단일 트랜잭션 내부에서 여러 데이터베이스 작업을 처리합니다.\n */\n async transaction<T>(cb: (tx: Omit<EasySqliteInstance, 'transaction' | 'close'>) => Promise<T>): Promise<T> {\n await this.execute('BEGIN TRANSACTION');\n try {\n const result = await cb(this);\n await this.execute('COMMIT');\n return result;\n } catch (err) {\n await this.execute('ROLLBACK');\n throw err;\n }\n }\n\n /**\n * 활성화된 데이터베이스 커넥션을 안전하게 닫습니다.\n */\n async close() {\n if (this.storageType === 'opfs') {\n if (this.promiser && this.opfsDbId) {\n this.log('OPFS 데이터베이스 커넥션 종료 중...');\n await this.promiser('close', { dbId: this.opfsDbId });\n this.opfsDbId = null;\n this.promiser = null;\n }\n } else {\n if (this.db) {\n this.log('로컬 데이터베이스 커넥션 종료 중...');\n // 남아있는 저장을 즉시 강제 수행합니다.\n if (this.storageType === 'indexeddb') {\n this.saveToIDB();\n }\n this.db.close();\n this.db = null;\n this.sqlite3 = null;\n }\n }\n }\n}\n\n// 동일한 데이터베이스의 중복 비동기 초기화를 방지하기 위한 전역 프로미스 캐시 맵\nconst initCache = new Map<string, Promise<EasySqlite>>();\n\n/**\n * EasySqlite 데이터베이스 인스턴스를 즉시 생성하고 연동 초기화하는 편리한 팩토리 헬퍼 함수입니다.\n * 중복 호출 시 이미 진행 중이거나 완료된 초기화 프로미스를 캐시하여 반환함으로써 중복 인스턴스 생성을 차단합니다.\n */\nexport function initEasySqlite(config: EasySqliteConfig): Promise<EasySqlite> {\n const cacheKey = `${config.storageType || 'indexeddb'}:${config.dbName}`;\n \n if (initCache.has(cacheKey)) {\n return initCache.get(cacheKey)!;\n }\n\n const promise = (async () => {\n try {\n const instance = new EasySqlite(config);\n return await instance.init();\n } catch (err) {\n // 초기화 실패 시 다음 호출이 재시도할 수 있도록 캐시에서 삭제합니다.\n initCache.delete(cacheKey);\n throw err;\n }\n })();\n\n initCache.set(cacheKey, promise);\n return promise;\n}\n","const DB_NAME = 'domi_sqlite_store';\nconst STORE_NAME = 'databases';\n\n/**\n * 로컬 IndexedDB 인스턴스를 오픈합니다.\n */\nfunction openIDB(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, 1);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n // 데이터베이스 백업들을 저장할 Object Store가 없다면 새로 생성합니다.\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME);\n }\n };\n\n request.onsuccess = () => {\n resolve(request.result);\n };\n\n request.onerror = () => {\n reject(request.error);\n };\n });\n}\n\n/**\n * IndexedDB로부터 저장된 SQLite 데이터베이스 파일(바이너리 데이터)을 Uint8Array 형태로 가져옵니다.\n */\nexport async function getDatabaseFromIndexedDB(dbName: string): Promise<Uint8Array | null> {\n try {\n const db = await openIDB();\n return await new Promise<Uint8Array | null>((resolve, reject) => {\n const transaction = db.transaction(STORE_NAME, 'readonly');\n const store = transaction.objectStore(STORE_NAME);\n const getReq = store.get(dbName);\n\n getReq.onsuccess = () => {\n resolve(getReq.result || null);\n };\n\n getReq.onerror = () => {\n reject(getReq.error);\n };\n });\n } catch (error) {\n console.error('[domi-sqlite] IndexedDB에서 DB를 로드하는데 실패했습니다:', error);\n return null;\n }\n}\n\n/**\n * SQLite 데이터베이스 파일(Uint8Array 바이너리)을 IndexedDB에 영구 보존합니다.\n */\nexport async function saveDatabaseToIndexedDB(dbName: string, data: Uint8Array): Promise<void> {\n try {\n const db = await openIDB();\n await new Promise<void>((resolve, reject) => {\n const transaction = db.transaction(STORE_NAME, 'readwrite');\n const store = transaction.objectStore(STORE_NAME);\n const putReq = store.put(data, dbName);\n\n putReq.onsuccess = () => {\n resolve();\n };\n\n putReq.onerror = () => {\n reject(putReq.error);\n };\n });\n } catch (error) {\n console.error('[domi-sqlite] IndexedDB에 DB를 백업하는데 실패했습니다:', error);\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// src/idb-helper.ts
|
|
2
|
+
var DB_NAME = "domi_sqlite_store";
|
|
3
|
+
var STORE_NAME = "databases";
|
|
4
|
+
function openIDB() {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const request = indexedDB.open(DB_NAME, 1);
|
|
7
|
+
request.onupgradeneeded = () => {
|
|
8
|
+
const db = request.result;
|
|
9
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
10
|
+
db.createObjectStore(STORE_NAME);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
request.onsuccess = () => {
|
|
14
|
+
resolve(request.result);
|
|
15
|
+
};
|
|
16
|
+
request.onerror = () => {
|
|
17
|
+
reject(request.error);
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async function getDatabaseFromIndexedDB(dbName) {
|
|
22
|
+
try {
|
|
23
|
+
const db = await openIDB();
|
|
24
|
+
return await new Promise((resolve, reject) => {
|
|
25
|
+
const transaction = db.transaction(STORE_NAME, "readonly");
|
|
26
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
27
|
+
const getReq = store.get(dbName);
|
|
28
|
+
getReq.onsuccess = () => {
|
|
29
|
+
resolve(getReq.result || null);
|
|
30
|
+
};
|
|
31
|
+
getReq.onerror = () => {
|
|
32
|
+
reject(getReq.error);
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("[domi-sqlite] IndexedDB\uC5D0\uC11C DB\uB97C \uB85C\uB4DC\uD558\uB294\uB370 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4:", error);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function saveDatabaseToIndexedDB(dbName, data) {
|
|
41
|
+
try {
|
|
42
|
+
const db = await openIDB();
|
|
43
|
+
await new Promise((resolve, reject) => {
|
|
44
|
+
const transaction = db.transaction(STORE_NAME, "readwrite");
|
|
45
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
46
|
+
const putReq = store.put(data, dbName);
|
|
47
|
+
putReq.onsuccess = () => {
|
|
48
|
+
resolve();
|
|
49
|
+
};
|
|
50
|
+
putReq.onerror = () => {
|
|
51
|
+
reject(putReq.error);
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error("[domi-sqlite] IndexedDB\uC5D0 DB\uB97C \uBC31\uC5C5\uD558\uB294\uB370 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4:", error);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/index.ts
|
|
61
|
+
var DEFAULT_WASM_URI = "https://unpkg.com/@sqlite.org/sqlite-wasm@3.46.1-build5/sqlite-wasm/jswasm/sqlite3.wasm";
|
|
62
|
+
var DEFAULT_WORKER_URI = "https://unpkg.com/@sqlite.org/sqlite-wasm@3.46.1-build5/sqlite-wasm/jswasm/sqlite3-worker1.js";
|
|
63
|
+
var EasySqlite = class {
|
|
64
|
+
config;
|
|
65
|
+
storageType;
|
|
66
|
+
db = null;
|
|
67
|
+
// 메모리/IndexedDB 모드용 (oo1.DB 인스턴스)
|
|
68
|
+
sqlite3 = null;
|
|
69
|
+
// 메모리/IndexedDB 모드용 모듈 캐시
|
|
70
|
+
promiser = null;
|
|
71
|
+
// OPFS 모드용 Worker Promiser
|
|
72
|
+
opfsDbId = null;
|
|
73
|
+
// OPFS 모드용 DB 고유 식별자
|
|
74
|
+
saveDebounced;
|
|
75
|
+
constructor(config) {
|
|
76
|
+
this.config = {
|
|
77
|
+
dbName: config.dbName,
|
|
78
|
+
storageType: config.storageType || "indexeddb",
|
|
79
|
+
wasmUri: config.wasmUri || DEFAULT_WASM_URI,
|
|
80
|
+
workerUri: config.workerUri || DEFAULT_WORKER_URI,
|
|
81
|
+
debug: config.debug || false
|
|
82
|
+
};
|
|
83
|
+
this.storageType = this.config.storageType;
|
|
84
|
+
this.saveDebounced = this.debounce(this.saveToIDB.bind(this), 250);
|
|
85
|
+
}
|
|
86
|
+
log(...args) {
|
|
87
|
+
if (this.config.debug) {
|
|
88
|
+
console.log("[domi-sqlite]", ...args);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
debounce(func, wait) {
|
|
92
|
+
let timeout;
|
|
93
|
+
return (...args) => {
|
|
94
|
+
clearTimeout(timeout);
|
|
95
|
+
timeout = setTimeout(() => func(...args), wait);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 설정된 스토리지 타입에 기반하여 SQLite WASM 연동을 초기화합니다.
|
|
100
|
+
*/
|
|
101
|
+
async init() {
|
|
102
|
+
if (typeof window === "undefined") {
|
|
103
|
+
this.log("\uC11C\uBC84 \uC0AC\uC774\uB4DC \uD658\uACBD \uAC10\uC9C0: init() \uC2E4\uD589\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4.");
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
if (this.storageType === "opfs") {
|
|
107
|
+
await this.initOpfs();
|
|
108
|
+
} else {
|
|
109
|
+
await this.initLocal();
|
|
110
|
+
}
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 로컬 메인 스레드 기반의 SQLite 인스턴스(Memory 또는 IndexedDB)를 초기화합니다.
|
|
115
|
+
*/
|
|
116
|
+
async initLocal() {
|
|
117
|
+
if (typeof window === "undefined") return;
|
|
118
|
+
this.log(`\uB85C\uCEEC SQLite \uCD08\uAE30\uD654 \uC911 (\uBAA8\uB4DC: ${this.storageType})...`);
|
|
119
|
+
const wasmUrl = this.config.wasmUri;
|
|
120
|
+
const { default: sqlite3InitModule } = await import("@sqlite.org/sqlite-wasm");
|
|
121
|
+
this.sqlite3 = await sqlite3InitModule({
|
|
122
|
+
locateFile: (file) => {
|
|
123
|
+
if (file === "sqlite3.wasm") {
|
|
124
|
+
return wasmUrl;
|
|
125
|
+
}
|
|
126
|
+
return file;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const oo1 = this.sqlite3.oo1;
|
|
130
|
+
this.db = new oo1.DB();
|
|
131
|
+
if (this.storageType === "indexeddb") {
|
|
132
|
+
const savedData = await getDatabaseFromIndexedDB(this.config.dbName);
|
|
133
|
+
if (savedData && savedData.byteLength > 0) {
|
|
134
|
+
this.log(`IndexedDB\uC5D0\uC11C \uAE30\uC874 \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uBC1C\uACAC (${savedData.byteLength} \uBC14\uC774\uD2B8). \uBCF5\uC6D0(Deserializing)\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4...`);
|
|
135
|
+
try {
|
|
136
|
+
const capi = this.sqlite3.capi;
|
|
137
|
+
const wasm = this.sqlite3.wasm;
|
|
138
|
+
const p = capi.sqlite3_malloc(savedData.byteLength);
|
|
139
|
+
if (!p) {
|
|
140
|
+
throw new Error("WebAssembly \uBA54\uBAA8\uB9AC \uD560\uB2F9\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (sqlite3_malloc).");
|
|
141
|
+
}
|
|
142
|
+
wasm.heap8u().set(savedData, p);
|
|
143
|
+
const deserializeFlags = capi.SQLITE_DESERIALIZE_FREEONCLOSE | capi.SQLITE_DESERIALIZE_RESIZEABLE;
|
|
144
|
+
const rc = capi.sqlite3_deserialize(
|
|
145
|
+
this.db.pointer,
|
|
146
|
+
"main",
|
|
147
|
+
p,
|
|
148
|
+
savedData.byteLength,
|
|
149
|
+
savedData.byteLength,
|
|
150
|
+
deserializeFlags
|
|
151
|
+
);
|
|
152
|
+
this.db.checkRc(rc);
|
|
153
|
+
this.log("\uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uBCF5\uC6D0 \uC131\uACF5.");
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error("[domi-sqlite] \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uBCF5\uC6D0 \uC2E4\uD328. \uC0C8 \uBE48 \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB85C \uC2DC\uC791\uD569\uB2C8\uB2E4.", err);
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
this.log("IndexedDB\uC5D0 \uC800\uC7A5\uB41C \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uC0C8\uB85C \uCD08\uAE30\uD654\uD569\uB2C8\uB2E4.");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Web Worker 및 Promiser API를 사용하여 OPFS 기반 SQLite 인스턴스를 초기화합니다.
|
|
164
|
+
*/
|
|
165
|
+
async initOpfs() {
|
|
166
|
+
this.log("Web Worker\uB97C \uC0AC\uC6A9\uD558\uC5EC OPFS SQLite \uCD08\uAE30\uD654 \uC911...");
|
|
167
|
+
const { sqlite3Worker1Promiser } = await import("@sqlite.org/sqlite-wasm");
|
|
168
|
+
return new Promise((resolve, reject) => {
|
|
169
|
+
try {
|
|
170
|
+
const workerUrl = this.config.workerUri;
|
|
171
|
+
const blobCode = `importScripts(${JSON.stringify(workerUrl)});`;
|
|
172
|
+
const blob = new Blob([blobCode], { type: "application/javascript" });
|
|
173
|
+
const worker = new Worker(URL.createObjectURL(blob));
|
|
174
|
+
const promiserInstance = sqlite3Worker1Promiser({
|
|
175
|
+
worker: () => worker,
|
|
176
|
+
onready: async () => {
|
|
177
|
+
this.promiser = promiserInstance;
|
|
178
|
+
try {
|
|
179
|
+
this.log(`OPFS\uB97C \uD1B5\uD574 \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uD30C\uC77C "${this.config.dbName}"\uC744(\uB97C) \uC5EC\uB294 \uC911...`);
|
|
180
|
+
const openResult = await this.promiser("open", {
|
|
181
|
+
filename: this.config.dbName,
|
|
182
|
+
vfs: "opfs"
|
|
183
|
+
});
|
|
184
|
+
this.opfsDbId = openResult.dbId;
|
|
185
|
+
this.log("OPFS \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC624\uD508 \uC131\uACF5. DB ID:", this.opfsDbId);
|
|
186
|
+
resolve();
|
|
187
|
+
} catch (err) {
|
|
188
|
+
reject(err);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
onerror: (err) => {
|
|
192
|
+
reject(err);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
} catch (err) {
|
|
196
|
+
reject(err);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* 현재 백그라운드 메모리의 SQLite 상태를 IndexedDB에 비동기로 백업(저장)합니다.
|
|
202
|
+
*/
|
|
203
|
+
saveToIDB() {
|
|
204
|
+
if (this.storageType !== "indexeddb" || !this.db || !this.sqlite3) return;
|
|
205
|
+
try {
|
|
206
|
+
this.log("\uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB97C IndexedDB\uC5D0 \uBC31\uC5C5\uD558\uB294 \uC911...");
|
|
207
|
+
const exported = this.sqlite3.capi.sqlite3_js_db_export(this.db.pointer);
|
|
208
|
+
if (exported) {
|
|
209
|
+
saveDatabaseToIndexedDB(this.config.dbName, exported).then(() => this.log("IndexedDB \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uBC31\uC5C5 \uC644\uB8CC.")).catch((err) => console.error("[domi-sqlite] IndexedDB \uBC31\uC5C5 \uC911 \uC5D0\uB7EC \uBC1C\uC0DD:", err));
|
|
210
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.error("[domi-sqlite] \uBC31\uC5C5\uC6A9 \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC5D1\uC2A4\uD3EC\uD2B8 \uC2E4\uD328:", err);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* SQL 쿼리를 실행하여 결과 데이터 행(Row) 객체 배열을 반환합니다.
|
|
217
|
+
*/
|
|
218
|
+
async query(sql, params = []) {
|
|
219
|
+
const res = await this.execute(sql, params);
|
|
220
|
+
return res.rows;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* SQL 문을 실행하고 결과 행(rows), 열 정의(columns), 영향받은 행 개수(rowsAffected) 등의 상세 상세 정보를 반환합니다.
|
|
224
|
+
*/
|
|
225
|
+
async execute(sql, params = []) {
|
|
226
|
+
if (typeof window === "undefined") {
|
|
227
|
+
return { rows: [], columns: [], rowsAffected: 0 };
|
|
228
|
+
}
|
|
229
|
+
if (this.storageType === "opfs") {
|
|
230
|
+
if (!this.promiser || !this.opfsDbId) {
|
|
231
|
+
throw new Error("\uB370\uC774\uD130\uBCA0\uC774\uC2A4\uAC00 \uCD08\uAE30\uD654\uB418\uC9C0 \uC54A\uC558\uAC70\uB098 \uC774\uBBF8 \uB2EB\uD600\uC788\uC2B5\uB2C8\uB2E4.");
|
|
232
|
+
}
|
|
233
|
+
this.log("OPFS \uCFFC\uB9AC \uC2E4\uD589:", sql, params);
|
|
234
|
+
const res = await this.promiser("exec", {
|
|
235
|
+
dbId: this.opfsDbId,
|
|
236
|
+
sql,
|
|
237
|
+
bind: params
|
|
238
|
+
});
|
|
239
|
+
const rows = [];
|
|
240
|
+
const columns = res.result.columnNames || [];
|
|
241
|
+
if (res.result.row) {
|
|
242
|
+
const valuesList = res.result.values || [];
|
|
243
|
+
for (const vals of valuesList) {
|
|
244
|
+
const rowObj = {};
|
|
245
|
+
columns.forEach((col, idx) => {
|
|
246
|
+
rowObj[col] = vals[idx];
|
|
247
|
+
});
|
|
248
|
+
rows.push(rowObj);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
rows,
|
|
253
|
+
columns,
|
|
254
|
+
rowsAffected: res.result.changeCount || 0
|
|
255
|
+
};
|
|
256
|
+
} else {
|
|
257
|
+
if (!this.db) {
|
|
258
|
+
throw new Error("\uB370\uC774\uD130\uBCA0\uC774\uC2A4\uAC00 \uCD08\uAE30\uD654\uB418\uC9C0 \uC54A\uC558\uAC70\uB098 \uC774\uBBF8 \uB2EB\uD600\uC788\uC2B5\uB2C8\uB2E4.");
|
|
259
|
+
}
|
|
260
|
+
this.log("\uB85C\uCEEC SQLite \uCFFC\uB9AC \uC2E4\uD589:", sql, params);
|
|
261
|
+
const columns = [];
|
|
262
|
+
const rows = [];
|
|
263
|
+
this.db.exec({
|
|
264
|
+
sql,
|
|
265
|
+
bind: params,
|
|
266
|
+
rowMode: "object",
|
|
267
|
+
columnNames: columns,
|
|
268
|
+
callback: (row) => {
|
|
269
|
+
rows.push(row);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
const isWriteQuery = /^\s*(insert|update|delete|create|drop|alter|replace)/i.test(sql);
|
|
273
|
+
if (this.storageType === "indexeddb" && isWriteQuery) {
|
|
274
|
+
this.saveDebounced();
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
rows,
|
|
278
|
+
columns,
|
|
279
|
+
rowsAffected: this.db.changes()
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* 단일 트랜잭션 내부에서 여러 데이터베이스 작업을 처리합니다.
|
|
285
|
+
*/
|
|
286
|
+
async transaction(cb) {
|
|
287
|
+
await this.execute("BEGIN TRANSACTION");
|
|
288
|
+
try {
|
|
289
|
+
const result = await cb(this);
|
|
290
|
+
await this.execute("COMMIT");
|
|
291
|
+
return result;
|
|
292
|
+
} catch (err) {
|
|
293
|
+
await this.execute("ROLLBACK");
|
|
294
|
+
throw err;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* 활성화된 데이터베이스 커넥션을 안전하게 닫습니다.
|
|
299
|
+
*/
|
|
300
|
+
async close() {
|
|
301
|
+
if (this.storageType === "opfs") {
|
|
302
|
+
if (this.promiser && this.opfsDbId) {
|
|
303
|
+
this.log("OPFS \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uCEE4\uB125\uC158 \uC885\uB8CC \uC911...");
|
|
304
|
+
await this.promiser("close", { dbId: this.opfsDbId });
|
|
305
|
+
this.opfsDbId = null;
|
|
306
|
+
this.promiser = null;
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
if (this.db) {
|
|
310
|
+
this.log("\uB85C\uCEEC \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uCEE4\uB125\uC158 \uC885\uB8CC \uC911...");
|
|
311
|
+
if (this.storageType === "indexeddb") {
|
|
312
|
+
this.saveToIDB();
|
|
313
|
+
}
|
|
314
|
+
this.db.close();
|
|
315
|
+
this.db = null;
|
|
316
|
+
this.sqlite3 = null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
var initCache = /* @__PURE__ */ new Map();
|
|
322
|
+
function initEasySqlite(config) {
|
|
323
|
+
const cacheKey = `${config.storageType || "indexeddb"}:${config.dbName}`;
|
|
324
|
+
if (initCache.has(cacheKey)) {
|
|
325
|
+
return initCache.get(cacheKey);
|
|
326
|
+
}
|
|
327
|
+
const promise = (async () => {
|
|
328
|
+
try {
|
|
329
|
+
const instance = new EasySqlite(config);
|
|
330
|
+
return await instance.init();
|
|
331
|
+
} catch (err) {
|
|
332
|
+
initCache.delete(cacheKey);
|
|
333
|
+
throw err;
|
|
334
|
+
}
|
|
335
|
+
})();
|
|
336
|
+
initCache.set(cacheKey, promise);
|
|
337
|
+
return promise;
|
|
338
|
+
}
|
|
339
|
+
export {
|
|
340
|
+
EasySqlite,
|
|
341
|
+
initEasySqlite
|
|
342
|
+
};
|
|
343
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/idb-helper.ts","../src/index.ts"],"sourcesContent":["const DB_NAME = 'domi_sqlite_store';\nconst STORE_NAME = 'databases';\n\n/**\n * 로컬 IndexedDB 인스턴스를 오픈합니다.\n */\nfunction openIDB(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, 1);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n // 데이터베이스 백업들을 저장할 Object Store가 없다면 새로 생성합니다.\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME);\n }\n };\n\n request.onsuccess = () => {\n resolve(request.result);\n };\n\n request.onerror = () => {\n reject(request.error);\n };\n });\n}\n\n/**\n * IndexedDB로부터 저장된 SQLite 데이터베이스 파일(바이너리 데이터)을 Uint8Array 형태로 가져옵니다.\n */\nexport async function getDatabaseFromIndexedDB(dbName: string): Promise<Uint8Array | null> {\n try {\n const db = await openIDB();\n return await new Promise<Uint8Array | null>((resolve, reject) => {\n const transaction = db.transaction(STORE_NAME, 'readonly');\n const store = transaction.objectStore(STORE_NAME);\n const getReq = store.get(dbName);\n\n getReq.onsuccess = () => {\n resolve(getReq.result || null);\n };\n\n getReq.onerror = () => {\n reject(getReq.error);\n };\n });\n } catch (error) {\n console.error('[domi-sqlite] IndexedDB에서 DB를 로드하는데 실패했습니다:', error);\n return null;\n }\n}\n\n/**\n * SQLite 데이터베이스 파일(Uint8Array 바이너리)을 IndexedDB에 영구 보존합니다.\n */\nexport async function saveDatabaseToIndexedDB(dbName: string, data: Uint8Array): Promise<void> {\n try {\n const db = await openIDB();\n await new Promise<void>((resolve, reject) => {\n const transaction = db.transaction(STORE_NAME, 'readwrite');\n const store = transaction.objectStore(STORE_NAME);\n const putReq = store.put(data, dbName);\n\n putReq.onsuccess = () => {\n resolve();\n };\n\n putReq.onerror = () => {\n reject(putReq.error);\n };\n });\n } catch (error) {\n console.error('[domi-sqlite] IndexedDB에 DB를 백업하는데 실패했습니다:', error);\n throw error;\n }\n}\n","import { getDatabaseFromIndexedDB, saveDatabaseToIndexedDB } from './idb-helper.js';\nimport { EasySqliteConfig, EasySqliteInstance, QueryResult, StorageType } from './types.js';\n\n// 번들러 구성 없이도 간편하게 연동할 수 있도록 제공하는 기본 CDN(unpkg) 경로 설정\nconst DEFAULT_WASM_URI = 'https://unpkg.com/@sqlite.org/sqlite-wasm@3.46.1-build5/sqlite-wasm/jswasm/sqlite3.wasm';\nconst DEFAULT_WORKER_URI = 'https://unpkg.com/@sqlite.org/sqlite-wasm@3.46.1-build5/sqlite-wasm/jswasm/sqlite3-worker1.js';\n\n/**\n * 로컬 메인 스레드 데이터베이스(IndexedDB / Memory 모드) 및\n * 백그라운드 워커 스레드 데이터베이스(OPFS 모드)를 관리하는 EasySqlite 구현체입니다.\n */\nexport class EasySqlite implements EasySqliteInstance {\n private config: Required<EasySqliteConfig>;\n private storageType: StorageType;\n private db: any = null; // 메모리/IndexedDB 모드용 (oo1.DB 인스턴스)\n private sqlite3: any = null; // 메모리/IndexedDB 모드용 모듈 캐시\n private promiser: any = null; // OPFS 모드용 Worker Promiser\n private opfsDbId: string | null = null; // OPFS 모드용 DB 고유 식별자\n private saveDebounced: () => void;\n\n constructor(config: EasySqliteConfig) {\n this.config = {\n dbName: config.dbName,\n storageType: config.storageType || 'indexeddb',\n wasmUri: config.wasmUri || DEFAULT_WASM_URI,\n workerUri: config.workerUri || DEFAULT_WORKER_URI,\n debug: config.debug || false,\n };\n this.storageType = this.config.storageType;\n\n // 연속적인 쿼리 요청이 올 때 IndexedDB에 과도하게 디스크 쓰기가 발생하는 것을 방지하기 위해 디바운싱 처리 (250ms)\n this.saveDebounced = this.debounce(this.saveToIDB.bind(this), 250);\n }\n\n private log(...args: any[]) {\n if (this.config.debug) {\n console.log('[domi-sqlite]', ...args);\n }\n }\n\n private debounce(func: (...args: any[]) => void, wait: number) {\n let timeout: any;\n return (...args: any[]) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => func(...args), wait);\n };\n }\n\n /**\n * 설정된 스토리지 타입에 기반하여 SQLite WASM 연동을 초기화합니다.\n */\n async init(): Promise<this> {\n // 서버 사이드 렌더링(SSR) 환경인 경우 초기화를 수행하지 않고 즉시 스킵합니다.\n if (typeof window === 'undefined') {\n this.log('서버 사이드 환경 감지: init() 실행을 건너뜁니다.');\n return this;\n }\n\n if (this.storageType === 'opfs') {\n await this.initOpfs();\n } else {\n await this.initLocal();\n }\n return this;\n }\n\n /**\n * 로컬 메인 스레드 기반의 SQLite 인스턴스(Memory 또는 IndexedDB)를 초기화합니다.\n */\n private async initLocal() {\n if (typeof window === 'undefined') return;\n this.log(`로컬 SQLite 초기화 중 (모드: ${this.storageType})...`);\n\n const wasmUrl = this.config.wasmUri;\n\n // 서버 사이드 컴파일 크래시를 방지하기 위해 클라이언트 런타임에 동적으로 모듈을 로드합니다.\n const { default: sqlite3InitModule } = await import('@sqlite.org/sqlite-wasm');\n\n this.sqlite3 = await sqlite3InitModule({\n locateFile: (file: string) => {\n if (file === 'sqlite3.wasm') {\n return wasmUrl;\n }\n return file;\n },\n });\n\n const oo1 = this.sqlite3.oo1;\n this.db = new oo1.DB();\n\n if (this.storageType === 'indexeddb') {\n const savedData = await getDatabaseFromIndexedDB(this.config.dbName);\n if (savedData && savedData.byteLength > 0) {\n this.log(`IndexedDB에서 기존 데이터베이스 발견 (${savedData.byteLength} 바이트). 복원(Deserializing)을 시작합니다...`);\n try {\n const capi = this.sqlite3.capi;\n const wasm = this.sqlite3.wasm;\n\n // SQLite의 sqlite3_realloc과 완벽히 호환되도록 capi.sqlite3_malloc을 통해 메모리를 명시적으로 할당합니다.\n const p = capi.sqlite3_malloc(savedData.byteLength);\n if (!p) {\n throw new Error('WebAssembly 메모리 할당에 실패했습니다 (sqlite3_malloc).');\n }\n\n // 할당된 WASM 메모리 영역에 기존 데이터베이스 바이너리를 복사합니다.\n wasm.heap8u().set(savedData, p);\n \n // 메모리 내 데이터베이스로 바이너리 값을 Deserialize(복원)합니다.\n // SQLITE_DESERIALIZE_FREEONCLOSE와 RESIZEABLE 조합은 버퍼가 sqlite3_malloc으로 할당된 경우에만 안전하게 동작합니다.\n const deserializeFlags = \n capi.SQLITE_DESERIALIZE_FREEONCLOSE | \n capi.SQLITE_DESERIALIZE_RESIZEABLE;\n\n const rc = capi.sqlite3_deserialize(\n this.db.pointer,\n 'main',\n p,\n savedData.byteLength,\n savedData.byteLength,\n deserializeFlags\n );\n\n this.db.checkRc(rc);\n this.log('데이터베이스 복원 성공.');\n } catch (err) {\n console.error('[domi-sqlite] 데이터베이스 복원 실패. 새 빈 데이터베이스로 시작합니다.', err);\n }\n } else {\n this.log('IndexedDB에 저장된 데이터베이스가 없습니다. 새로 초기화합니다.');\n }\n }\n }\n\n /**\n * Web Worker 및 Promiser API를 사용하여 OPFS 기반 SQLite 인스턴스를 초기화합니다.\n */\n private async initOpfs() {\n this.log('Web Worker를 사용하여 OPFS SQLite 초기화 중...');\n\n // 빌드 타임에 OPFS Worker와 메인 스레드 간 프로토콜을 설정하는 모듈을 다이내믹 임포트합니다.\n const { sqlite3Worker1Promiser } = await import('@sqlite.org/sqlite-wasm');\n\n return new Promise<void>((resolve, reject) => {\n try {\n const workerUrl = this.config.workerUri;\n // CDN 호스팅 시 발생할 수 있는 동일출처정책(Same-Origin Policy) 우회를 위한 Blob 프록시 워커 처리\n const blobCode = `importScripts(${JSON.stringify(workerUrl)});`;\n const blob = new Blob([blobCode], { type: 'application/javascript' });\n const worker = new Worker(URL.createObjectURL(blob));\n\n const promiserInstance = sqlite3Worker1Promiser({\n worker: () => worker,\n onready: async () => {\n this.promiser = promiserInstance;\n try {\n this.log(`OPFS를 통해 데이터베이스 파일 \"${this.config.dbName}\"을(를) 여는 중...`);\n const openResult = await this.promiser('open', {\n filename: this.config.dbName,\n vfs: 'opfs',\n });\n this.opfsDbId = openResult.dbId;\n this.log('OPFS 데이터베이스 오픈 성공. DB ID:', this.opfsDbId);\n resolve();\n } catch (err) {\n reject(err);\n }\n },\n onerror: (err: any) => {\n reject(err);\n },\n });\n } catch (err) {\n reject(err);\n }\n });\n }\n\n /**\n * 현재 백그라운드 메모리의 SQLite 상태를 IndexedDB에 비동기로 백업(저장)합니다.\n */\n private saveToIDB() {\n if (this.storageType !== 'indexeddb' || !this.db || !this.sqlite3) return;\n\n try {\n this.log('데이터베이스를 IndexedDB에 백업하는 중...');\n // 메모리에 로드된 SQLite 인스턴스 전체를 Uint8Array 바이너리로 내보냅니다.\n const exported = this.sqlite3.capi.sqlite3_js_db_export(this.db.pointer);\n if (exported) {\n saveDatabaseToIndexedDB(this.config.dbName, exported)\n .then(() => this.log('IndexedDB 데이터베이스 백업 완료.'))\n .catch((err) => console.error('[domi-sqlite] IndexedDB 백업 중 에러 발생:', err));\n }\n } catch (err) {\n console.error('[domi-sqlite] 백업용 데이터베이스 엑스포트 실패:', err);\n }\n }\n\n /**\n * SQL 쿼리를 실행하여 결과 데이터 행(Row) 객체 배열을 반환합니다.\n */\n async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {\n const res = await this.execute(sql, params);\n return res.rows as T[];\n }\n\n /**\n * SQL 문을 실행하고 결과 행(rows), 열 정의(columns), 영향받은 행 개수(rowsAffected) 등의 상세 상세 정보를 반환합니다.\n */\n async execute(sql: string, params: any[] = []): Promise<QueryResult> {\n // SSR 환경인 경우 쿼리 실행을 방지하고 빈 결과를 안전하게 반환합니다.\n if (typeof window === 'undefined') {\n return { rows: [], columns: [], rowsAffected: 0 };\n }\n\n if (this.storageType === 'opfs') {\n if (!this.promiser || !this.opfsDbId) {\n throw new Error('데이터베이스가 초기화되지 않았거나 이미 닫혀있습니다.');\n }\n\n this.log('OPFS 쿼리 실행:', sql, params);\n const res = await this.promiser('exec', {\n dbId: this.opfsDbId,\n sql,\n bind: params,\n });\n\n // Promiser API 출력 구조에 맞춰 결과를 재구성합니다.\n const rows: any[] = [];\n const columns = res.result.columnNames || [];\n\n if (res.result.row) {\n // 결과값을 순회하며 컬럼명과 매핑되는 행 객체 구조로 뱐환합니다.\n const valuesList = res.result.values || [];\n for (const vals of valuesList) {\n const rowObj: any = {};\n columns.forEach((col: string, idx: number) => {\n rowObj[col] = vals[idx];\n });\n rows.push(rowObj);\n }\n }\n\n return {\n rows,\n columns,\n rowsAffected: res.result.changeCount || 0,\n };\n } else {\n if (!this.db) {\n throw new Error('데이터베이스가 초기화되지 않았거나 이미 닫혀있습니다.');\n }\n\n this.log('로컬 SQLite 쿼리 실행:', sql, params);\n const columns: string[] = [];\n const rows: any[] = [];\n\n this.db.exec({\n sql,\n bind: params,\n rowMode: 'object',\n columnNames: columns,\n callback: (row: any) => {\n rows.push(row);\n },\n });\n\n // 데이터가 수정되거나 추가되는 쓰기 작업 쿼리(INSERT, UPDATE 등)가 발생한 경우 백업을 수행합니다.\n const isWriteQuery = /^\\s*(insert|update|delete|create|drop|alter|replace)/i.test(sql);\n if (this.storageType === 'indexeddb' && isWriteQuery) {\n this.saveDebounced();\n }\n\n return {\n rows,\n columns,\n rowsAffected: this.db.changes(),\n };\n }\n }\n\n /**\n * 단일 트랜잭션 내부에서 여러 데이터베이스 작업을 처리합니다.\n */\n async transaction<T>(cb: (tx: Omit<EasySqliteInstance, 'transaction' | 'close'>) => Promise<T>): Promise<T> {\n await this.execute('BEGIN TRANSACTION');\n try {\n const result = await cb(this);\n await this.execute('COMMIT');\n return result;\n } catch (err) {\n await this.execute('ROLLBACK');\n throw err;\n }\n }\n\n /**\n * 활성화된 데이터베이스 커넥션을 안전하게 닫습니다.\n */\n async close() {\n if (this.storageType === 'opfs') {\n if (this.promiser && this.opfsDbId) {\n this.log('OPFS 데이터베이스 커넥션 종료 중...');\n await this.promiser('close', { dbId: this.opfsDbId });\n this.opfsDbId = null;\n this.promiser = null;\n }\n } else {\n if (this.db) {\n this.log('로컬 데이터베이스 커넥션 종료 중...');\n // 남아있는 저장을 즉시 강제 수행합니다.\n if (this.storageType === 'indexeddb') {\n this.saveToIDB();\n }\n this.db.close();\n this.db = null;\n this.sqlite3 = null;\n }\n }\n }\n}\n\n// 동일한 데이터베이스의 중복 비동기 초기화를 방지하기 위한 전역 프로미스 캐시 맵\nconst initCache = new Map<string, Promise<EasySqlite>>();\n\n/**\n * EasySqlite 데이터베이스 인스턴스를 즉시 생성하고 연동 초기화하는 편리한 팩토리 헬퍼 함수입니다.\n * 중복 호출 시 이미 진행 중이거나 완료된 초기화 프로미스를 캐시하여 반환함으로써 중복 인스턴스 생성을 차단합니다.\n */\nexport function initEasySqlite(config: EasySqliteConfig): Promise<EasySqlite> {\n const cacheKey = `${config.storageType || 'indexeddb'}:${config.dbName}`;\n \n if (initCache.has(cacheKey)) {\n return initCache.get(cacheKey)!;\n }\n\n const promise = (async () => {\n try {\n const instance = new EasySqlite(config);\n return await instance.init();\n } catch (err) {\n // 초기화 실패 시 다음 호출이 재시도할 수 있도록 캐시에서 삭제합니다.\n initCache.delete(cacheKey);\n throw err;\n }\n })();\n\n initCache.set(cacheKey, promise);\n return promise;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@h_domi/domi-indexed-sqlite",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Easy SQLite-WASM wrapper for Nuxt and Vue 3 with IndexedDB backup support.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"dev": "tsup --watch",
|
|
14
|
+
"lint": "eslint \"src/**/*.ts\""
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"sqlite",
|
|
18
|
+
"sqlite-wasm",
|
|
19
|
+
"indexeddb",
|
|
20
|
+
"vue",
|
|
21
|
+
"nuxt",
|
|
22
|
+
"typescript"
|
|
23
|
+
],
|
|
24
|
+
"author": "",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@sqlite.org/sqlite-wasm": "3.46.1-build5"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@typescript-eslint/eslint-plugin": "^7.13.1",
|
|
31
|
+
"@typescript-eslint/parser": "^7.13.1",
|
|
32
|
+
"eslint": "^8.57.0",
|
|
33
|
+
"tsup": "^8.1.0",
|
|
34
|
+
"typescript": "^5.4.5"
|
|
35
|
+
}
|
|
36
|
+
}
|