@owlmeans/web-db 0.1.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/LICENSE +21 -0
- package/README.md +784 -0
- package/build/consts.d.ts +2 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +3 -0
- package/build/consts.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/build/service.d.ts +9 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +46 -0
- package/build/service.js.map +1 -0
- package/build/types.d.ts +5 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +36 -0
- package/src/consts.ts +3 -0
- package/src/index.ts +4 -0
- package/src/service.ts +67 -0
- package/src/types.ts +6 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
# @owlmeans/web-db
|
|
2
|
+
|
|
3
|
+
Web database implementation for OwlMeans Common applications. This package provides a robust client-side database service built on IndexedDB through the `idb-keyval` library, offering persistent storage for web applications with a simple key-value interface.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@owlmeans/web-db` package implements the `ClientDbService` interface from `@owlmeans/client-resource`, providing web-specific database functionality. It offers:
|
|
8
|
+
|
|
9
|
+
- **IndexedDB Integration**: Leverages browser's IndexedDB for persistent storage
|
|
10
|
+
- **Simple Key-Value API**: Clean, promise-based interface for data operations
|
|
11
|
+
- **Multiple Database Support**: Manage multiple isolated database instances
|
|
12
|
+
- **Automatic Namespacing**: Prevents key collisions between different resources
|
|
13
|
+
- **Service Integration**: Seamless integration with OwlMeans context system
|
|
14
|
+
- **Data Persistence**: Survives browser sessions and page reloads
|
|
15
|
+
- **Cross-Tab Synchronization**: Data changes are visible across browser tabs
|
|
16
|
+
|
|
17
|
+
This package is part of the OwlMeans database implementation family:
|
|
18
|
+
- **@owlmeans/client-resource**: Client resource management interfaces
|
|
19
|
+
- **@owlmeans/web-db**: Web IndexedDB implementation *(this package)*
|
|
20
|
+
- **@owlmeans/native-db**: React Native database implementation
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @owlmeans/web-db
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Core Concepts
|
|
29
|
+
|
|
30
|
+
### IndexedDB Foundation
|
|
31
|
+
Built on IndexedDB, the standard web database API, providing reliable persistent storage that works offline and survives browser restarts.
|
|
32
|
+
|
|
33
|
+
### Namespaced Storage
|
|
34
|
+
Each database instance uses a namespace prefix to prevent key collisions, allowing multiple resources to share the same underlying storage safely.
|
|
35
|
+
|
|
36
|
+
### Service Architecture
|
|
37
|
+
Implements the ClientDbService interface, making it compatible with the OwlMeans resource system and context management.
|
|
38
|
+
|
|
39
|
+
## API Reference
|
|
40
|
+
|
|
41
|
+
### Factory Functions
|
|
42
|
+
|
|
43
|
+
#### `makeWebDbService(alias?: string): WebDbService`
|
|
44
|
+
|
|
45
|
+
Creates a web database service instance that manages IndexedDB storage.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { makeWebDbService } from '@owlmeans/web-db'
|
|
49
|
+
|
|
50
|
+
const dbService = makeWebDbService('main-db')
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Parameters:**
|
|
54
|
+
- `alias`: string (optional) - Service alias for registration, defaults to 'client-db'
|
|
55
|
+
|
|
56
|
+
**Returns:** WebDbService instance ready for registration with context
|
|
57
|
+
|
|
58
|
+
#### `appendWebDbService<C, T>(context: T, alias?: string): T`
|
|
59
|
+
|
|
60
|
+
Appends a web database service to the application context.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { appendWebDbService } from '@owlmeans/web-db'
|
|
64
|
+
import { makeClientContext } from '@owlmeans/client-context'
|
|
65
|
+
|
|
66
|
+
const context = makeClientContext(config)
|
|
67
|
+
appendWebDbService(context)
|
|
68
|
+
|
|
69
|
+
// Access the service
|
|
70
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Parameters:**
|
|
74
|
+
- `context`: T - The client context to append the service to
|
|
75
|
+
- `alias`: string (optional) - Service alias, defaults to 'client-db'
|
|
76
|
+
|
|
77
|
+
**Returns:** Enhanced context with the registered database service
|
|
78
|
+
|
|
79
|
+
### Core Interfaces
|
|
80
|
+
|
|
81
|
+
#### `WebDbService`
|
|
82
|
+
|
|
83
|
+
Main database service interface that extends both InitializedService and ClientDbService.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
interface WebDbService extends InitializedService, ClientDbService {
|
|
87
|
+
// Inherited from ClientDbService:
|
|
88
|
+
initialize(alias?: string): Promise<ClientDb> // Create database instance
|
|
89
|
+
erase(): Promise<void> // Clear all data
|
|
90
|
+
|
|
91
|
+
// Inherited from InitializedService:
|
|
92
|
+
initialized: boolean // Initialization status
|
|
93
|
+
init?(): Promise<void> // Initialization method
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### `ClientDb`
|
|
98
|
+
|
|
99
|
+
Database instance interface providing key-value operations.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
interface ClientDb {
|
|
103
|
+
get<T>(id: string): Promise<T> // Retrieve value by key
|
|
104
|
+
set<T>(id: string, value: T): Promise<void> // Store value by key
|
|
105
|
+
has(id: string): Promise<boolean> // Check if key exists
|
|
106
|
+
del(id: string): Promise<boolean> // Delete key and return success
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Database Methods Detailed Reference
|
|
111
|
+
|
|
112
|
+
#### `initialize(alias?: string): Promise<ClientDb>`
|
|
113
|
+
|
|
114
|
+
**Purpose**: Creates and returns a database instance with the specified namespace
|
|
115
|
+
|
|
116
|
+
**Behavior**:
|
|
117
|
+
- Creates a new ClientDb instance if one doesn't exist for the alias
|
|
118
|
+
- Returns existing instance if already created for the alias
|
|
119
|
+
- Automatically namespaces all keys with the alias prefix
|
|
120
|
+
- Uses IndexedDB through idb-keyval for persistent storage
|
|
121
|
+
|
|
122
|
+
**Usage**: Called by resources to get their database instance
|
|
123
|
+
|
|
124
|
+
**Parameters**:
|
|
125
|
+
- `alias`: string (optional) - Namespace for the database instance
|
|
126
|
+
|
|
127
|
+
**Returns**: Promise that resolves to ClientDb instance
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
131
|
+
|
|
132
|
+
// Create database for users
|
|
133
|
+
const userDb = await dbService.initialize('users')
|
|
134
|
+
|
|
135
|
+
// Create database for settings
|
|
136
|
+
const settingsDb = await dbService.initialize('settings')
|
|
137
|
+
|
|
138
|
+
// Each database is isolated from others
|
|
139
|
+
await userDb.set('john', { name: 'John Doe' })
|
|
140
|
+
await settingsDb.set('theme', 'dark')
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### `erase(): Promise<void>`
|
|
144
|
+
|
|
145
|
+
**Purpose**: Completely clears all data from IndexedDB
|
|
146
|
+
|
|
147
|
+
**Behavior**:
|
|
148
|
+
- Removes all data across all database instances
|
|
149
|
+
- Irreversible operation that clears entire IndexedDB store
|
|
150
|
+
- Affects all namespaces and aliases
|
|
151
|
+
|
|
152
|
+
**Usage**: Data cleanup, reset operations, testing
|
|
153
|
+
|
|
154
|
+
**Warning**: This removes ALL data stored by the application
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
158
|
+
|
|
159
|
+
// WARNING: This clears ALL application data
|
|
160
|
+
await dbService.erase()
|
|
161
|
+
console.log('All database data has been erased')
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### ClientDb Instance Methods
|
|
165
|
+
|
|
166
|
+
#### `get<T>(id: string): Promise<T>`
|
|
167
|
+
|
|
168
|
+
**Purpose**: Retrieves a value from storage by its key
|
|
169
|
+
|
|
170
|
+
**Behavior**:
|
|
171
|
+
- Returns the stored value if key exists
|
|
172
|
+
- Returns undefined if key doesn't exist
|
|
173
|
+
- Automatically deserializes complex objects
|
|
174
|
+
- Type-safe with TypeScript generics
|
|
175
|
+
|
|
176
|
+
**Usage**: Loading stored data
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const userDb = await dbService.initialize('users')
|
|
180
|
+
|
|
181
|
+
// Get user data
|
|
182
|
+
const user = await userDb.get<UserData>('john')
|
|
183
|
+
if (user) {
|
|
184
|
+
console.log('User found:', user.name)
|
|
185
|
+
} else {
|
|
186
|
+
console.log('User not found')
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### `set<T>(id: string, value: T): Promise<void>`
|
|
191
|
+
|
|
192
|
+
**Purpose**: Stores a value in the database with the specified key
|
|
193
|
+
|
|
194
|
+
**Behavior**:
|
|
195
|
+
- Stores any serializable JavaScript value
|
|
196
|
+
- Overwrites existing value if key already exists
|
|
197
|
+
- Automatically serializes complex objects
|
|
198
|
+
- Type-safe with TypeScript generics
|
|
199
|
+
|
|
200
|
+
**Usage**: Saving data to persistent storage
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const userDb = await dbService.initialize('users')
|
|
204
|
+
|
|
205
|
+
// Store user data
|
|
206
|
+
await userDb.set('john', {
|
|
207
|
+
name: 'John Doe',
|
|
208
|
+
email: 'john@example.com',
|
|
209
|
+
preferences: { theme: 'dark' }
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// Store simple values
|
|
213
|
+
await userDb.set('last-login', new Date())
|
|
214
|
+
await userDb.set('session-count', 42)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### `has(id: string): Promise<boolean>`
|
|
218
|
+
|
|
219
|
+
**Purpose**: Checks if a key exists in the database
|
|
220
|
+
|
|
221
|
+
**Behavior**:
|
|
222
|
+
- Returns true if key exists (even if value is null/undefined)
|
|
223
|
+
- Returns false if key doesn't exist
|
|
224
|
+
- Efficient check without retrieving the full value
|
|
225
|
+
|
|
226
|
+
**Usage**: Existence checks before operations
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const userDb = await dbService.initialize('users')
|
|
230
|
+
|
|
231
|
+
// Check if user exists before loading
|
|
232
|
+
const userExists = await userDb.has('john')
|
|
233
|
+
if (userExists) {
|
|
234
|
+
const user = await userDb.get('john')
|
|
235
|
+
console.log('Loading existing user:', user.name)
|
|
236
|
+
} else {
|
|
237
|
+
console.log('User not found, creating new user')
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### `del(id: string): Promise<boolean>`
|
|
242
|
+
|
|
243
|
+
**Purpose**: Deletes a key from the database
|
|
244
|
+
|
|
245
|
+
**Behavior**:
|
|
246
|
+
- Removes the key and its value from storage
|
|
247
|
+
- Returns true if key existed and was deleted
|
|
248
|
+
- Returns false if key didn't exist
|
|
249
|
+
- Atomic operation
|
|
250
|
+
|
|
251
|
+
**Usage**: Removing data from storage
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
const userDb = await dbService.initialize('users')
|
|
255
|
+
|
|
256
|
+
// Delete user data
|
|
257
|
+
const wasDeleted = await userDb.del('john')
|
|
258
|
+
if (wasDeleted) {
|
|
259
|
+
console.log('User john was deleted')
|
|
260
|
+
} else {
|
|
261
|
+
console.log('User john was not found')
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Verify deletion
|
|
265
|
+
const stillExists = await userDb.has('john')
|
|
266
|
+
console.log('User still exists:', stillExists) // false
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Constants
|
|
270
|
+
|
|
271
|
+
#### `DEFAULT_ALIAS`
|
|
272
|
+
Default service alias (`'client-db'`) imported from `@owlmeans/client-resource`.
|
|
273
|
+
|
|
274
|
+
## Usage Examples
|
|
275
|
+
|
|
276
|
+
### Basic Database Setup
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { appendWebDbService } from '@owlmeans/web-db'
|
|
280
|
+
import { makeClientContext } from '@owlmeans/client-context'
|
|
281
|
+
|
|
282
|
+
// Create context with database service
|
|
283
|
+
const context = makeClientContext(config)
|
|
284
|
+
appendWebDbService(context)
|
|
285
|
+
|
|
286
|
+
// Initialize context
|
|
287
|
+
await context.configure().init()
|
|
288
|
+
|
|
289
|
+
// Access database service
|
|
290
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
291
|
+
console.log('Database service ready:', dbService.initialized)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Multiple Database Instances
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
298
|
+
|
|
299
|
+
// Create separate databases for different purposes
|
|
300
|
+
const userDb = await dbService.initialize('users')
|
|
301
|
+
const settingsDb = await dbService.initialize('settings')
|
|
302
|
+
const cacheDb = await dbService.initialize('cache')
|
|
303
|
+
|
|
304
|
+
// Each database is completely isolated
|
|
305
|
+
await userDb.set('current-user', { id: '123', name: 'John' })
|
|
306
|
+
await settingsDb.set('theme', 'dark')
|
|
307
|
+
await cacheDb.set('api-response', { data: [...], timestamp: Date.now() })
|
|
308
|
+
|
|
309
|
+
// No interference between databases
|
|
310
|
+
const userTheme = await userDb.get('theme') // undefined
|
|
311
|
+
const settingsTheme = await settingsDb.get('theme') // 'dark'
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### User Data Management
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
interface UserProfile {
|
|
318
|
+
id: string
|
|
319
|
+
name: string
|
|
320
|
+
email: string
|
|
321
|
+
preferences: {
|
|
322
|
+
theme: 'light' | 'dark'
|
|
323
|
+
language: string
|
|
324
|
+
notifications: boolean
|
|
325
|
+
}
|
|
326
|
+
lastLogin: Date
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
330
|
+
const userDb = await dbService.initialize('user-profiles')
|
|
331
|
+
|
|
332
|
+
// Save user profile
|
|
333
|
+
const saveUserProfile = async (profile: UserProfile) => {
|
|
334
|
+
await userDb.set(profile.id, profile)
|
|
335
|
+
console.log('Profile saved for:', profile.name)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Load user profile
|
|
339
|
+
const loadUserProfile = async (userId: string): Promise<UserProfile | null> => {
|
|
340
|
+
const hasProfile = await userDb.has(userId)
|
|
341
|
+
if (!hasProfile) {
|
|
342
|
+
return null
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return await userDb.get<UserProfile>(userId)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Update last login
|
|
349
|
+
const updateLastLogin = async (userId: string) => {
|
|
350
|
+
const profile = await loadUserProfile(userId)
|
|
351
|
+
if (profile) {
|
|
352
|
+
profile.lastLogin = new Date()
|
|
353
|
+
await saveUserProfile(profile)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Usage
|
|
358
|
+
await saveUserProfile({
|
|
359
|
+
id: 'user-123',
|
|
360
|
+
name: 'Alice Johnson',
|
|
361
|
+
email: 'alice@example.com',
|
|
362
|
+
preferences: {
|
|
363
|
+
theme: 'dark',
|
|
364
|
+
language: 'en',
|
|
365
|
+
notifications: true
|
|
366
|
+
},
|
|
367
|
+
lastLogin: new Date()
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
const profile = await loadUserProfile('user-123')
|
|
371
|
+
console.log('Loaded profile:', profile?.name)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Settings Persistence
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
interface AppSettings {
|
|
378
|
+
theme: 'light' | 'dark' | 'auto'
|
|
379
|
+
language: string
|
|
380
|
+
autoSave: boolean
|
|
381
|
+
apiEndpoint: string
|
|
382
|
+
debugMode: boolean
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
386
|
+
const settingsDb = await dbService.initialize('app-settings')
|
|
387
|
+
|
|
388
|
+
class SettingsManager {
|
|
389
|
+
private static readonly SETTINGS_KEY = 'app-settings'
|
|
390
|
+
|
|
391
|
+
static async load(): Promise<AppSettings> {
|
|
392
|
+
const hasSettings = await settingsDb.has(this.SETTINGS_KEY)
|
|
393
|
+
if (!hasSettings) {
|
|
394
|
+
return this.getDefaultSettings()
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const saved = await settingsDb.get<AppSettings>(this.SETTINGS_KEY)
|
|
398
|
+
return { ...this.getDefaultSettings(), ...saved }
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
static async save(settings: Partial<AppSettings>): Promise<void> {
|
|
402
|
+
const current = await this.load()
|
|
403
|
+
const updated = { ...current, ...settings }
|
|
404
|
+
await settingsDb.set(this.SETTINGS_KEY, updated)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
static async reset(): Promise<void> {
|
|
408
|
+
await settingsDb.del(this.SETTINGS_KEY)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private static getDefaultSettings(): AppSettings {
|
|
412
|
+
return {
|
|
413
|
+
theme: 'auto',
|
|
414
|
+
language: 'en',
|
|
415
|
+
autoSave: true,
|
|
416
|
+
apiEndpoint: '/api',
|
|
417
|
+
debugMode: false
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Usage
|
|
423
|
+
const settings = await SettingsManager.load()
|
|
424
|
+
console.log('Current theme:', settings.theme)
|
|
425
|
+
|
|
426
|
+
await SettingsManager.save({ theme: 'dark', debugMode: true })
|
|
427
|
+
console.log('Settings updated')
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Caching System
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
interface CacheEntry<T> {
|
|
434
|
+
data: T
|
|
435
|
+
timestamp: number
|
|
436
|
+
ttl: number // time to live in milliseconds
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
class WebCache {
|
|
440
|
+
private db: ClientDb
|
|
441
|
+
|
|
442
|
+
constructor(private dbService: WebDbService) {}
|
|
443
|
+
|
|
444
|
+
async initialize() {
|
|
445
|
+
this.db = await this.dbService.initialize('cache')
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async set<T>(key: string, data: T, ttlMs: number = 300000): Promise<void> {
|
|
449
|
+
const entry: CacheEntry<T> = {
|
|
450
|
+
data,
|
|
451
|
+
timestamp: Date.now(),
|
|
452
|
+
ttl: ttlMs
|
|
453
|
+
}
|
|
454
|
+
await this.db.set(key, entry)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async get<T>(key: string): Promise<T | null> {
|
|
458
|
+
const hasKey = await this.db.has(key)
|
|
459
|
+
if (!hasKey) {
|
|
460
|
+
return null
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const entry = await this.db.get<CacheEntry<T>>(key)
|
|
464
|
+
if (!entry) {
|
|
465
|
+
return null
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Check if expired
|
|
469
|
+
if (Date.now() - entry.timestamp > entry.ttl) {
|
|
470
|
+
await this.db.del(key)
|
|
471
|
+
return null
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return entry.data
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async clear(): Promise<void> {
|
|
478
|
+
// Note: This is a simplified version
|
|
479
|
+
// In practice, you'd need to track cache keys
|
|
480
|
+
console.log('Cache clear not implemented - use dbService.erase() for full clear')
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Usage
|
|
485
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
486
|
+
const cache = new WebCache(dbService)
|
|
487
|
+
await cache.initialize()
|
|
488
|
+
|
|
489
|
+
// Cache API response for 5 minutes
|
|
490
|
+
await cache.set('user-list', userData, 5 * 60 * 1000)
|
|
491
|
+
|
|
492
|
+
// Try to get from cache
|
|
493
|
+
const cachedUsers = await cache.get('user-list')
|
|
494
|
+
if (cachedUsers) {
|
|
495
|
+
console.log('Using cached data:', cachedUsers)
|
|
496
|
+
} else {
|
|
497
|
+
console.log('Cache miss, fetching fresh data')
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Data Migration
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
class DataMigration {
|
|
505
|
+
private dbService: WebDbService
|
|
506
|
+
|
|
507
|
+
constructor(dbService: WebDbService) {
|
|
508
|
+
this.dbService = dbService
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async migrateUserData() {
|
|
512
|
+
const userDb = await this.dbService.initialize('users')
|
|
513
|
+
const migrationDb = await this.dbService.initialize('migration')
|
|
514
|
+
|
|
515
|
+
// Check if migration already done
|
|
516
|
+
const migrationDone = await migrationDb.has('user-data-v2')
|
|
517
|
+
if (migrationDone) {
|
|
518
|
+
console.log('Migration already completed')
|
|
519
|
+
return
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Simulate migration from old format to new format
|
|
523
|
+
const oldUserData = await userDb.get('old-format-user')
|
|
524
|
+
if (oldUserData) {
|
|
525
|
+
const newUserData = this.transformUserData(oldUserData)
|
|
526
|
+
await userDb.set('new-format-user', newUserData)
|
|
527
|
+
await userDb.del('old-format-user')
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Mark migration as complete
|
|
531
|
+
await migrationDb.set('user-data-v2', { completed: new Date() })
|
|
532
|
+
console.log('User data migration completed')
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private transformUserData(oldData: any): any {
|
|
536
|
+
// Transform old data format to new format
|
|
537
|
+
return {
|
|
538
|
+
...oldData,
|
|
539
|
+
version: 2,
|
|
540
|
+
migrated: true
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Usage
|
|
546
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
547
|
+
const migration = new DataMigration(dbService)
|
|
548
|
+
await migration.migrateUserData()
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Integration with React Components
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
import React, { useState, useEffect } from 'react'
|
|
555
|
+
import { useContext } from '@owlmeans/client'
|
|
556
|
+
|
|
557
|
+
interface UserPreferences {
|
|
558
|
+
theme: string
|
|
559
|
+
language: string
|
|
560
|
+
notifications: boolean
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function UserSettings() {
|
|
564
|
+
const context = useContext()
|
|
565
|
+
const [preferences, setPreferences] = useState<UserPreferences | null>(null)
|
|
566
|
+
const [loading, setLoading] = useState(true)
|
|
567
|
+
|
|
568
|
+
useEffect(() => {
|
|
569
|
+
const loadPreferences = async () => {
|
|
570
|
+
try {
|
|
571
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
572
|
+
const settingsDb = await dbService.initialize('user-settings')
|
|
573
|
+
|
|
574
|
+
const saved = await settingsDb.get<UserPreferences>('preferences')
|
|
575
|
+
setPreferences(saved || {
|
|
576
|
+
theme: 'light',
|
|
577
|
+
language: 'en',
|
|
578
|
+
notifications: true
|
|
579
|
+
})
|
|
580
|
+
} catch (error) {
|
|
581
|
+
console.error('Failed to load preferences:', error)
|
|
582
|
+
} finally {
|
|
583
|
+
setLoading(false)
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
loadPreferences()
|
|
588
|
+
}, [])
|
|
589
|
+
|
|
590
|
+
const savePreferences = async (newPrefs: UserPreferences) => {
|
|
591
|
+
try {
|
|
592
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
593
|
+
const settingsDb = await dbService.initialize('user-settings')
|
|
594
|
+
|
|
595
|
+
await settingsDb.set('preferences', newPrefs)
|
|
596
|
+
setPreferences(newPrefs)
|
|
597
|
+
console.log('Preferences saved')
|
|
598
|
+
} catch (error) {
|
|
599
|
+
console.error('Failed to save preferences:', error)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (loading) {
|
|
604
|
+
return <div>Loading preferences...</div>
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return (
|
|
608
|
+
<div>
|
|
609
|
+
<h3>User Settings</h3>
|
|
610
|
+
<label>
|
|
611
|
+
Theme:
|
|
612
|
+
<select
|
|
613
|
+
value={preferences?.theme}
|
|
614
|
+
onChange={(e) => savePreferences({ ...preferences!, theme: e.target.value })}
|
|
615
|
+
>
|
|
616
|
+
<option value="light">Light</option>
|
|
617
|
+
<option value="dark">Dark</option>
|
|
618
|
+
</select>
|
|
619
|
+
</label>
|
|
620
|
+
|
|
621
|
+
<label>
|
|
622
|
+
<input
|
|
623
|
+
type="checkbox"
|
|
624
|
+
checked={preferences?.notifications}
|
|
625
|
+
onChange={(e) => savePreferences({ ...preferences!, notifications: e.target.checked })}
|
|
626
|
+
/>
|
|
627
|
+
Enable notifications
|
|
628
|
+
</label>
|
|
629
|
+
</div>
|
|
630
|
+
)
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Advanced Database Operations
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
class DatabaseManager {
|
|
638
|
+
private dbService: WebDbService
|
|
639
|
+
|
|
640
|
+
constructor(dbService: WebDbService) {
|
|
641
|
+
this.dbService = dbService
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async exportData(): Promise<Record<string, any>> {
|
|
645
|
+
const databases = ['users', 'settings', 'cache']
|
|
646
|
+
const exportData: Record<string, any> = {}
|
|
647
|
+
|
|
648
|
+
for (const dbName of databases) {
|
|
649
|
+
const db = await this.dbService.initialize(dbName)
|
|
650
|
+
exportData[dbName] = {}
|
|
651
|
+
|
|
652
|
+
// Note: This is simplified - in practice you'd need to track keys
|
|
653
|
+
// or implement a proper iteration mechanism
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return exportData
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async importData(data: Record<string, any>): Promise<void> {
|
|
660
|
+
for (const [dbName, dbData] of Object.entries(data)) {
|
|
661
|
+
const db = await this.dbService.initialize(dbName)
|
|
662
|
+
|
|
663
|
+
for (const [key, value] of Object.entries(dbData)) {
|
|
664
|
+
await db.set(key, value)
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async getStorageUsage(): Promise<{ estimated: boolean; quota?: number; usage?: number }> {
|
|
670
|
+
if ('storage' in navigator && 'estimate' in navigator.storage) {
|
|
671
|
+
return await navigator.storage.estimate()
|
|
672
|
+
}
|
|
673
|
+
return { estimated: false }
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async clearAllData(): Promise<void> {
|
|
677
|
+
await this.dbService.erase()
|
|
678
|
+
console.log('All application data cleared')
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Usage
|
|
683
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
684
|
+
const dbManager = new DatabaseManager(dbService)
|
|
685
|
+
|
|
686
|
+
// Check storage usage
|
|
687
|
+
const usage = await dbManager.getStorageUsage()
|
|
688
|
+
console.log('Storage usage:', usage)
|
|
689
|
+
|
|
690
|
+
// Clear all data if needed
|
|
691
|
+
// await dbManager.clearAllData()
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
## Browser Compatibility
|
|
695
|
+
|
|
696
|
+
The package works in all modern browsers that support IndexedDB:
|
|
697
|
+
|
|
698
|
+
- **Chrome**: 24+
|
|
699
|
+
- **Firefox**: 16+
|
|
700
|
+
- **Safari**: 10+
|
|
701
|
+
- **Edge**: 12+
|
|
702
|
+
- **Mobile browsers**: iOS 10+, Android 4.4+
|
|
703
|
+
|
|
704
|
+
## Performance Considerations
|
|
705
|
+
|
|
706
|
+
1. **Async Operations**: All database operations are asynchronous and return promises
|
|
707
|
+
2. **Namespacing**: Each database instance is isolated for performance and organization
|
|
708
|
+
3. **IndexedDB Limits**: Be aware of browser storage quotas (typically 50MB+)
|
|
709
|
+
4. **Serialization**: Complex objects are automatically serialized/deserialized
|
|
710
|
+
5. **Batching**: Consider batching multiple operations when possible
|
|
711
|
+
|
|
712
|
+
## Error Handling
|
|
713
|
+
|
|
714
|
+
```typescript
|
|
715
|
+
import { WebDbService } from '@owlmeans/web-db'
|
|
716
|
+
|
|
717
|
+
const handleDatabaseOperation = async () => {
|
|
718
|
+
try {
|
|
719
|
+
const dbService = context.service<WebDbService>('client-db')
|
|
720
|
+
const db = await dbService.initialize('users')
|
|
721
|
+
|
|
722
|
+
await db.set('user123', userData)
|
|
723
|
+
} catch (error) {
|
|
724
|
+
if (error.name === 'QuotaExceededError') {
|
|
725
|
+
console.error('Storage quota exceeded')
|
|
726
|
+
} else if (error.name === 'DataError') {
|
|
727
|
+
console.error('Invalid data for storage')
|
|
728
|
+
} else {
|
|
729
|
+
console.error('Database error:', error.message)
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
## Integration with Other Packages
|
|
736
|
+
|
|
737
|
+
### Client Resource Integration
|
|
738
|
+
```typescript
|
|
739
|
+
import { appendClientResource } from '@owlmeans/client-resource'
|
|
740
|
+
import { appendWebDbService } from '@owlmeans/web-db'
|
|
741
|
+
|
|
742
|
+
// Setup database service first
|
|
743
|
+
appendWebDbService(context)
|
|
744
|
+
|
|
745
|
+
// Then setup resources that will use it
|
|
746
|
+
appendClientResource(context, 'users')
|
|
747
|
+
appendClientResource(context, 'settings')
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Context Integration
|
|
751
|
+
```typescript
|
|
752
|
+
import { makeClientContext } from '@owlmeans/client-context'
|
|
753
|
+
import { appendWebDbService } from '@owlmeans/web-db'
|
|
754
|
+
|
|
755
|
+
const context = makeClientContext(config)
|
|
756
|
+
appendWebDbService(context)
|
|
757
|
+
|
|
758
|
+
await context.configure().init()
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
## Best Practices
|
|
762
|
+
|
|
763
|
+
1. **Namespace Usage**: Use descriptive database aliases to organize data
|
|
764
|
+
2. **Error Handling**: Always handle database operation errors gracefully
|
|
765
|
+
3. **Data Validation**: Validate data before storing to prevent corruption
|
|
766
|
+
4. **Storage Limits**: Monitor storage usage and implement cleanup strategies
|
|
767
|
+
5. **Performance**: Use appropriate data structures and avoid storing large objects
|
|
768
|
+
6. **Testing**: Test database operations across different browsers
|
|
769
|
+
7. **Migration**: Plan for data format changes with migration strategies
|
|
770
|
+
|
|
771
|
+
## Dependencies
|
|
772
|
+
|
|
773
|
+
This package depends on:
|
|
774
|
+
- `@owlmeans/client-context` - Client context management
|
|
775
|
+
- `@owlmeans/client-resource` - Client resource interfaces
|
|
776
|
+
- `@owlmeans/context` - Core context system
|
|
777
|
+
- `idb-keyval` - IndexedDB key-value library
|
|
778
|
+
|
|
779
|
+
## Related Packages
|
|
780
|
+
|
|
781
|
+
- [`@owlmeans/client-resource`](../client-resource) - Client resource management
|
|
782
|
+
- [`@owlmeans/native-db`](../native-db) - React Native database implementation
|
|
783
|
+
- [`@owlmeans/client-context`](../client-context) - Client context management
|
|
784
|
+
- [`@owlmeans/web-client`](../web-client) - Web client with database integration
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,cAAmB,CAAA"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAE5D,MAAM,CAAC,MAAM,aAAa,GAAG,gBAAgB,CAAA"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WebDbService } from './types.js';
|
|
2
|
+
import type { ClientConfig, ClientContext } from '@owlmeans/client-context';
|
|
3
|
+
type Config = ClientConfig;
|
|
4
|
+
interface Context<C extends Config = Config> extends ClientContext<C> {
|
|
5
|
+
}
|
|
6
|
+
export declare const makeWebDbService: (alias?: string) => WebDbService;
|
|
7
|
+
export declare const appendWebDbService: <C extends Config, T extends Context<C> = Context<C>>(context: T, alias?: string) => T;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAG9C,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAE3E,KAAK,MAAM,GAAG,YAAY,CAAA;AAC1B,UAAU,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI;AAEzE,eAAO,MAAM,gBAAgB,WAAW,MAAM,KAAmB,YA8ChE,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,wBAC9D,CAAC,UAAS,MAAM,KACxB,CAMF,CAAA"}
|
package/build/service.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createService } from '@owlmeans/context';
|
|
2
|
+
import { DEFAULT_ALIAS } from './consts.js';
|
|
3
|
+
import { get, set, del, clear } from 'idb-keyval';
|
|
4
|
+
export const makeWebDbService = (alias = DEFAULT_ALIAS) => {
|
|
5
|
+
const stores = {};
|
|
6
|
+
const service = createService(alias, {
|
|
7
|
+
initialize: async (alias) => {
|
|
8
|
+
alias = alias ?? DEFAULT_ALIAS;
|
|
9
|
+
if (stores[alias] != null) {
|
|
10
|
+
return stores[alias];
|
|
11
|
+
}
|
|
12
|
+
const _key = (id) => alias + ':' + id;
|
|
13
|
+
const db = {
|
|
14
|
+
get: async (id) => {
|
|
15
|
+
return await get(_key(id));
|
|
16
|
+
},
|
|
17
|
+
set: async (id, value) => {
|
|
18
|
+
await set(_key(id), value);
|
|
19
|
+
},
|
|
20
|
+
has: async (id) => {
|
|
21
|
+
return null != await get(_key(id));
|
|
22
|
+
},
|
|
23
|
+
del: async (id) => {
|
|
24
|
+
const has = await db.has(id);
|
|
25
|
+
if (has) {
|
|
26
|
+
await del(_key(id));
|
|
27
|
+
}
|
|
28
|
+
return has;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
return stores[alias] = db;
|
|
32
|
+
},
|
|
33
|
+
erase: async () => {
|
|
34
|
+
clear();
|
|
35
|
+
}
|
|
36
|
+
}, service => async () => {
|
|
37
|
+
service.initialized = true;
|
|
38
|
+
});
|
|
39
|
+
return service;
|
|
40
|
+
};
|
|
41
|
+
export const appendWebDbService = (context, alias = DEFAULT_ALIAS) => {
|
|
42
|
+
const service = makeWebDbService(alias);
|
|
43
|
+
context.registerService(service);
|
|
44
|
+
return context;
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAG3C,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAMjD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,QAAgB,aAAa,EAAgB,EAAE;IAC9E,MAAM,MAAM,GAA6B,EAAE,CAAA;IAC3C,MAAM,OAAO,GAAG,aAAa,CAAe,KAAK,EAAE;QACjD,UAAU,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YACxB,KAAK,GAAG,KAAK,IAAI,aAAa,CAAA;YAE9B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC1B,OAAO,MAAM,CAAC,KAAK,CAAC,CAAA;YACtB,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,KAAK,GAAG,GAAG,GAAG,EAAE,CAAA;YAE7C,MAAM,EAAE,GAAa;gBACnB,GAAG,EAAE,KAAK,EAAK,EAAU,EAAE,EAAE;oBAC3B,OAAO,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAM,CAAA;gBACjC,CAAC;gBAED,GAAG,EAAE,KAAK,EAAK,EAAU,EAAE,KAAQ,EAAE,EAAE;oBACrC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAA;gBAC5B,CAAC;gBAED,GAAG,EAAE,KAAK,EAAC,EAAE,EAAC,EAAE;oBACd,OAAO,IAAI,IAAI,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;gBACpC,CAAC;gBAED,GAAG,EAAE,KAAK,EAAC,EAAE,EAAC,EAAE;oBACd,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;oBAC5B,IAAI,GAAG,EAAE,CAAC;wBACR,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;oBACrB,CAAC;oBAED,OAAO,GAAG,CAAA;gBACZ,CAAC;aACF,CAAA;YAED,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAA;QAC3B,CAAC;QAED,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,KAAK,EAAE,CAAA;QACT,CAAC;KACF,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE;QACvB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEF,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,OAAU,EAAE,QAAgB,aAAa,EACtC,EAAE;IACL,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAA;IAEvC,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;IAEhC,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAE3D,MAAM,WAAW,YAAa,SAAQ,kBAAkB,EAAE,eAAe;CAExE"}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owlmeans/web-db",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc -b",
|
|
7
|
+
"dev": "sleep 360 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
|
|
8
|
+
"watch": "tsc -b -w --preserveWatchOutput --pretty"
|
|
9
|
+
},
|
|
10
|
+
"main": "build/index.js",
|
|
11
|
+
"module": "build/index.js",
|
|
12
|
+
"types": "build/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./build/index.js",
|
|
16
|
+
"require": "./build/index.js",
|
|
17
|
+
"default": "./build/index.js",
|
|
18
|
+
"module": "./build/index.js",
|
|
19
|
+
"types": "./build/index.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@owlmeans/client-context": "^0.1.0",
|
|
24
|
+
"@owlmeans/client-resource": "^0.1.0",
|
|
25
|
+
"@owlmeans/context": "^0.1.0",
|
|
26
|
+
"idb-keyval": "^6.2.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"nodemon": "^3.1.7",
|
|
30
|
+
"typescript": "^5.6.3"
|
|
31
|
+
},
|
|
32
|
+
"private": false,
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/consts.ts
ADDED
package/src/index.ts
ADDED
package/src/service.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createService } from '@owlmeans/context'
|
|
2
|
+
import { DEFAULT_ALIAS } from './consts.js'
|
|
3
|
+
import type { WebDbService } from './types.js'
|
|
4
|
+
import type { ClientDb } from '@owlmeans/client-resource'
|
|
5
|
+
import { get, set, del, clear } from 'idb-keyval'
|
|
6
|
+
import type { ClientConfig, ClientContext } from '@owlmeans/client-context'
|
|
7
|
+
|
|
8
|
+
type Config = ClientConfig
|
|
9
|
+
interface Context<C extends Config = Config> extends ClientContext<C> { }
|
|
10
|
+
|
|
11
|
+
export const makeWebDbService = (alias: string = DEFAULT_ALIAS): WebDbService => {
|
|
12
|
+
const stores: Record<string, ClientDb> = {}
|
|
13
|
+
const service = createService<WebDbService>(alias, {
|
|
14
|
+
initialize: async alias => {
|
|
15
|
+
alias = alias ?? DEFAULT_ALIAS
|
|
16
|
+
|
|
17
|
+
if (stores[alias] != null) {
|
|
18
|
+
return stores[alias]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const _key = (id: string) => alias + ':' + id
|
|
22
|
+
|
|
23
|
+
const db: ClientDb = {
|
|
24
|
+
get: async <T>(id: string) => {
|
|
25
|
+
return await get(_key(id)) as T
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
set: async <T>(id: string, value: T) => {
|
|
29
|
+
await set(_key(id), value)
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
has: async id => {
|
|
33
|
+
return null != await get(_key(id))
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
del: async id => {
|
|
37
|
+
const has = await db.has(id)
|
|
38
|
+
if (has) {
|
|
39
|
+
await del(_key(id))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return has
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return stores[alias] = db
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
erase: async () => {
|
|
50
|
+
clear()
|
|
51
|
+
}
|
|
52
|
+
}, service => async () => {
|
|
53
|
+
service.initialized = true
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return service
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const appendWebDbService = <C extends Config, T extends Context<C> = Context<C>>(
|
|
60
|
+
context: T, alias: string = DEFAULT_ALIAS
|
|
61
|
+
): T => {
|
|
62
|
+
const service = makeWebDbService(alias)
|
|
63
|
+
|
|
64
|
+
context.registerService(service)
|
|
65
|
+
|
|
66
|
+
return context
|
|
67
|
+
}
|
package/src/types.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"../tsconfig.default.json",
|
|
4
|
+
],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"rootDir": "./src/", /* Specify the root folder within your source files. */
|
|
7
|
+
"outDir": "./build/", /* Specify an output folder for all emitted files. */
|
|
8
|
+
},
|
|
9
|
+
"exclude": [
|
|
10
|
+
"./dist/**/*",
|
|
11
|
+
"./build/**/*",
|
|
12
|
+
"./*.ts"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/consts.ts","./src/index.ts","./src/service.ts","./src/types.ts"],"version":"5.6.3"}
|