@owlmeans/web-db 0.1.1 → 0.1.3
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 +1 -1
- package/README.md +29 -750
- package/package.json +7 -5
- package/tsconfig.json +5 -9
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 OwlMeans Common — Fullstack typescript framework
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,784 +1,63 @@
|
|
|
1
1
|
# @owlmeans/web-db
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
IndexedDB-backed client database service for OwlMeans web applications.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
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
|
|
7
|
+
- `makeWebDbService(alias?)` — creates an IndexedDB-backed `ClientDbService`
|
|
8
|
+
- `appendWebDbService(context, alias?)` — registers the DB service in the context
|
|
9
|
+
- Implements `ClientDb`: `get`, `set`, `has`, `del`, `erase` over `idb-keyval`
|
|
10
|
+
- Used internally by `@owlmeans/web-client`'s `makeContext` to back `client-resource` storage
|
|
21
11
|
|
|
22
12
|
## Installation
|
|
23
13
|
|
|
24
14
|
```bash
|
|
25
|
-
|
|
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
|
-
}
|
|
15
|
+
bun add @owlmeans/web-db
|
|
239
16
|
```
|
|
240
17
|
|
|
241
|
-
|
|
18
|
+
## Usage
|
|
242
19
|
|
|
243
|
-
|
|
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
|
|
20
|
+
This package is registered automatically when using `makeContext` from `@owlmeans/web-client`. Direct use is only needed for custom context setup:
|
|
277
21
|
|
|
278
22
|
```typescript
|
|
279
23
|
import { appendWebDbService } from '@owlmeans/web-db'
|
|
280
|
-
import { makeClientContext } from '@owlmeans/client-context'
|
|
281
24
|
|
|
282
|
-
// Create context with database service
|
|
283
|
-
const context = makeClientContext(config)
|
|
284
25
|
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
26
|
```
|
|
313
27
|
|
|
314
|
-
|
|
28
|
+
Access the DB directly:
|
|
315
29
|
|
|
316
30
|
```typescript
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
}
|
|
31
|
+
import { DEFAULT_ALIAS } from '@owlmeans/web-db'
|
|
32
|
+
import type { WebDbService } from '@owlmeans/web-db'
|
|
356
33
|
|
|
357
|
-
|
|
358
|
-
await
|
|
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
|
-
})
|
|
34
|
+
const dbService = context.service<WebDbService>(DEFAULT_ALIAS)
|
|
35
|
+
const db = await dbService.initialize('my-store')
|
|
369
36
|
|
|
370
|
-
|
|
371
|
-
|
|
37
|
+
await db.set('key', { value: 123 })
|
|
38
|
+
const record = await db.get<MyType>('key')
|
|
39
|
+
await db.del('key')
|
|
372
40
|
```
|
|
373
41
|
|
|
374
|
-
|
|
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
|
-
```
|
|
42
|
+
## API
|
|
500
43
|
|
|
501
|
-
###
|
|
44
|
+
### `makeWebDbService(alias?): WebDbService`
|
|
502
45
|
|
|
503
|
-
|
|
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
|
-
```
|
|
46
|
+
Creates an IndexedDB service. `alias` defaults to `DEFAULT_ALIAS`.
|
|
760
47
|
|
|
761
|
-
|
|
48
|
+
### `appendWebDbService<C, T>(context, alias?): T`
|
|
762
49
|
|
|
763
|
-
|
|
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
|
|
50
|
+
Registers the DB service in the context.
|
|
770
51
|
|
|
771
|
-
|
|
52
|
+
### `WebDbService`
|
|
772
53
|
|
|
773
|
-
|
|
774
|
-
-
|
|
775
|
-
-
|
|
776
|
-
-
|
|
777
|
-
- `
|
|
54
|
+
Extends `ClientDbService` with `initialize(alias?)` returning a `ClientDb`:
|
|
55
|
+
- `get<T>(id): Promise<T>`
|
|
56
|
+
- `set<T>(id, value): Promise<void>`
|
|
57
|
+
- `has(id): Promise<boolean>`
|
|
58
|
+
- `del(id): Promise<boolean>`
|
|
778
59
|
|
|
779
60
|
## Related Packages
|
|
780
61
|
|
|
781
|
-
- [`@owlmeans/client-resource`](../client-resource)
|
|
782
|
-
- [`@owlmeans/
|
|
783
|
-
- [`@owlmeans/client-context`](../client-context) - Client context management
|
|
784
|
-
- [`@owlmeans/web-client`](../web-client) - Web client with database integration
|
|
62
|
+
- [`@owlmeans/client-resource`](../client-resource) — `ClientDbService` interface this implements
|
|
63
|
+
- [`@owlmeans/web-client`](../web-client) — calls `appendWebDbService` inside `makeContext`
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/web-db",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"scripts": {
|
|
6
7
|
"build": "tsc -b",
|
|
@@ -20,14 +21,15 @@
|
|
|
20
21
|
}
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
|
-
"@owlmeans/client-context": "^0.1.
|
|
24
|
-
"@owlmeans/client-resource": "^0.1.
|
|
25
|
-
"@owlmeans/context": "^0.1.
|
|
24
|
+
"@owlmeans/client-context": "^0.1.3",
|
|
25
|
+
"@owlmeans/client-resource": "^0.1.3",
|
|
26
|
+
"@owlmeans/context": "^0.1.3",
|
|
26
27
|
"idb-keyval": "^6.2.1"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
30
|
+
"@owlmeans/dep-config": "workspace:*",
|
|
29
31
|
"nodemon": "^3.1.11",
|
|
30
|
-
"typescript": "^
|
|
32
|
+
"typescript": "^6.0.2"
|
|
31
33
|
},
|
|
32
34
|
"publishConfig": {
|
|
33
35
|
"access": "public"
|
package/tsconfig.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": [
|
|
3
|
-
"
|
|
3
|
+
"@owlmeans/dep-config/tsconfig.base.json"
|
|
4
4
|
],
|
|
5
5
|
"compilerOptions": {
|
|
6
|
-
"rootDir": "./src/",
|
|
7
|
-
"outDir": "./build/"
|
|
6
|
+
"rootDir": "./src/",
|
|
7
|
+
"outDir": "./build/"
|
|
8
8
|
},
|
|
9
|
-
"exclude": [
|
|
10
|
-
|
|
11
|
-
"./build/**/*",
|
|
12
|
-
"./*.ts"
|
|
13
|
-
]
|
|
14
|
-
}
|
|
9
|
+
"exclude": ["./dist/**/*", "./build/**/*", "./*.ts"]
|
|
10
|
+
}
|