@decaf-ts/for-couchdb 0.3.1 → 0.3.2
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.md +646 -144
- package/README.md +371 -1
- package/dist/for-couchdb.cjs +733 -24
- package/dist/for-couchdb.esm.cjs +733 -24
- package/lib/adapter.cjs +186 -1
- package/lib/adapter.d.ts +234 -0
- package/lib/constants.cjs +29 -1
- package/lib/constants.d.ts +28 -0
- package/lib/errors.cjs +18 -1
- package/lib/errors.d.ts +17 -0
- package/lib/esm/adapter.d.ts +234 -0
- package/lib/esm/adapter.js +186 -1
- package/lib/esm/constants.d.ts +28 -0
- package/lib/esm/constants.js +29 -1
- package/lib/esm/errors.d.ts +17 -0
- package/lib/esm/errors.js +18 -1
- package/lib/esm/index.d.ts +6 -13
- package/lib/esm/index.js +7 -14
- package/lib/esm/indexes/generator.d.ts +47 -0
- package/lib/esm/indexes/generator.js +58 -1
- package/lib/esm/interfaces/CouchDBRepository.d.ts +10 -0
- package/lib/esm/interfaces/CouchDBRepository.js +1 -1
- package/lib/esm/model/CouchDBSequence.d.ts +19 -0
- package/lib/esm/model/CouchDBSequence.js +12 -1
- package/lib/esm/query/Paginator.d.ts +111 -0
- package/lib/esm/query/Paginator.js +117 -8
- package/lib/esm/query/Statement.d.ts +134 -0
- package/lib/esm/query/Statement.js +143 -1
- package/lib/esm/query/constants.d.ts +42 -0
- package/lib/esm/query/constants.js +43 -1
- package/lib/esm/query/translate.d.ts +31 -0
- package/lib/esm/query/translate.js +32 -1
- package/lib/esm/sequences/Sequence.d.ts +0 -2
- package/lib/esm/sequences/Sequence.js +2 -4
- package/lib/esm/types.d.ts +55 -12
- package/lib/esm/types.js +1 -1
- package/lib/esm/utils.d.ts +105 -0
- package/lib/esm/utils.js +106 -1
- package/lib/index.cjs +7 -14
- package/lib/index.d.ts +6 -13
- package/lib/indexes/generator.cjs +58 -1
- package/lib/indexes/generator.d.ts +47 -0
- package/lib/interfaces/CouchDBRepository.cjs +1 -1
- package/lib/interfaces/CouchDBRepository.d.ts +10 -0
- package/lib/model/CouchDBSequence.cjs +12 -1
- package/lib/model/CouchDBSequence.d.ts +19 -0
- package/lib/query/Paginator.cjs +117 -8
- package/lib/query/Paginator.d.ts +111 -0
- package/lib/query/Statement.cjs +143 -1
- package/lib/query/Statement.d.ts +134 -0
- package/lib/query/constants.cjs +43 -1
- package/lib/query/constants.d.ts +42 -0
- package/lib/query/translate.cjs +32 -1
- package/lib/query/translate.d.ts +31 -0
- package/lib/sequences/Sequence.cjs +2 -4
- package/lib/sequences/Sequence.d.ts +0 -2
- package/lib/types.cjs +1 -1
- package/lib/types.d.ts +55 -12
- package/lib/utils.cjs +106 -1
- package/lib/utils.d.ts +105 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# Decaf CouchDB Module
|
|
4
4
|
|
|
5
|
+
A TypeScript adapter for CouchDB database operations, providing a seamless integration with the Decaf.ts framework. This module offers a comprehensive set of tools for working with CouchDB databases, including support for Mango queries, document operations, sequence management, and indexing capabilities.
|
|
6
|
+
|
|
7
|
+
|
|
5
8
|

|
|
6
9
|

|
|
7
10
|

|
|
@@ -33,8 +36,35 @@ Documentation available [here](https://decaf-ts.github.io/for-couchdb/)
|
|
|
33
36
|
|
|
34
37
|
### Description
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
The Decaf CouchDB Module is a versatile persistence layer designed to provide seamless integration between the Decaf.ts framework and CouchDB databases. It offers a comprehensive set of tools and abstractions that simplify working with CouchDB's unique features while maintaining type safety and following best practices.
|
|
40
|
+
|
|
41
|
+
#### Core Components
|
|
42
|
+
|
|
43
|
+
1. **CouchDBAdapter**: An abstract base class that provides the foundation for CouchDB database operations. It handles CRUD operations, sequence management, and error handling. Developers can extend this class to create custom adapters tailored to their specific needs.
|
|
44
|
+
|
|
45
|
+
2. **Query System**: A powerful query builder with support for CouchDB's Mango queries:
|
|
46
|
+
- **CouchDBStatement**: Provides a fluent interface for building type-safe Mango queries
|
|
47
|
+
- **CouchDBPaginator**: Implements pagination for query results using CouchDB's bookmark system
|
|
48
|
+
- **Operator Translation**: Converts Decaf.ts core operators to CouchDB Mango operators
|
|
37
49
|
|
|
50
|
+
3. **Indexing**: Tools for creating and managing CouchDB indexes:
|
|
51
|
+
- **Index Generation**: Automatically generates appropriate index configurations based on model metadata
|
|
52
|
+
- **Index Management**: Utilities for creating and maintaining indexes
|
|
53
|
+
|
|
54
|
+
4. **Sequence Management**: A robust system for generating sequential IDs:
|
|
55
|
+
- **CouchDBSequence**: Implements the Sequence interface for CouchDB
|
|
56
|
+
- **Sequence Model**: Provides a data model for storing sequence information
|
|
57
|
+
|
|
58
|
+
5. **Error Handling**: Specialized error types and utilities for handling CouchDB-specific errors:
|
|
59
|
+
- **Error Translation**: Converts CouchDB error codes and messages to appropriate Decaf.ts error types
|
|
60
|
+
- **IndexError**: Specialized error for index-related issues
|
|
61
|
+
|
|
62
|
+
6. **Utilities**: Helper functions for common CouchDB operations:
|
|
63
|
+
- **Authentication**: Functions for handling CouchDB authentication
|
|
64
|
+
- **Connection Management**: Utilities for managing database connections
|
|
65
|
+
- **Document Processing**: Tools for processing CouchDB documents
|
|
66
|
+
|
|
67
|
+
This module serves as a bridge between your application and CouchDB, abstracting away the complexities of the database while providing a type-safe, consistent API that integrates seamlessly with the rest of the Decaf.ts ecosystem.
|
|
38
68
|
|
|
39
69
|
|
|
40
70
|
### How to Use
|
|
@@ -42,7 +72,347 @@ A very versatile persistence layer. from smart contracts, Digital wallets or jus
|
|
|
42
72
|
- [Initial Setup](./tutorials/For%20Developers.md#_initial-setup_)
|
|
43
73
|
- [Installation](./tutorials/For%20Developers.md#installation)
|
|
44
74
|
|
|
75
|
+
## Creating a CouchDB Adapter
|
|
76
|
+
|
|
77
|
+
To use the CouchDB module, you need to create a concrete implementation of the CouchDBAdapter class:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { CouchDBAdapter } from '@decaf-ts/for-couchdb';
|
|
81
|
+
import { Constructor, Model } from '@decaf-ts/decorator-validation';
|
|
82
|
+
import { MangoQuery } from '@decaf-ts/for-couchdb';
|
|
83
|
+
import { generateIndexes } from '@decaf-ts/for-couchdb';
|
|
84
|
+
import * as nano from 'nano';
|
|
85
|
+
|
|
86
|
+
// Define your scope, flags, and context types
|
|
87
|
+
interface MyScope {
|
|
88
|
+
config: {
|
|
89
|
+
couchdb: {
|
|
90
|
+
url: string;
|
|
91
|
+
username: string;
|
|
92
|
+
password: string;
|
|
93
|
+
database: string;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class MyCouchDBAdapter extends CouchDBAdapter<MyScope, MyFlags, MyContext> {
|
|
99
|
+
private db: any;
|
|
100
|
+
|
|
101
|
+
constructor(scope: MyScope) {
|
|
102
|
+
super(scope, 'my-couchdb', 'my-alias');
|
|
103
|
+
|
|
104
|
+
// Initialize connection to CouchDB
|
|
105
|
+
const { url, username, password, database } = scope.config.couchdb;
|
|
106
|
+
const connection = nano(url);
|
|
107
|
+
this.db = wrapDocumentScope(connection, database, username, password);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Implement abstract methods
|
|
111
|
+
async index<M extends Model>(...models: Constructor<M>[]): Promise<void> {
|
|
112
|
+
const indexes = generateIndexes(models);
|
|
113
|
+
for (const index of indexes) {
|
|
114
|
+
try {
|
|
115
|
+
await this.db.createIndex(index);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
throw this.parseError(error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async raw<R>(rawInput: MangoQuery, docsOnly: boolean): Promise<R> {
|
|
123
|
+
try {
|
|
124
|
+
const result = await this.db.find(rawInput);
|
|
125
|
+
return docsOnly ? result.docs : result;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw this.parseError(error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async create(tableName: string, id: string | number, model: Record<string, any>, ...args: any[]): Promise<Record<string, any>> {
|
|
132
|
+
try {
|
|
133
|
+
const result = await this.db.insert(model);
|
|
134
|
+
return this.assignMetadata(model, result.rev);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw this.parseError(error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async read(tableName: string, id: string | number, ...args: any[]): Promise<Record<string, any>> {
|
|
141
|
+
try {
|
|
142
|
+
const docId = this.generateId(tableName, id);
|
|
143
|
+
const doc = await this.db.get(docId);
|
|
144
|
+
return this.assignMetadata(doc, doc._rev);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
throw this.parseError(error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async update(tableName: string, id: string | number, model: Record<string, any>, ...args: any[]): Promise<Record<string, any>> {
|
|
151
|
+
try {
|
|
152
|
+
const result = await this.db.insert(model);
|
|
153
|
+
return this.assignMetadata(model, result.rev);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
throw this.parseError(error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async delete(tableName: string, id: string | number, ...args: any[]): Promise<Record<string, any>> {
|
|
160
|
+
try {
|
|
161
|
+
const docId = this.generateId(tableName, id);
|
|
162
|
+
const doc = await this.db.get(docId);
|
|
163
|
+
const result = await this.db.destroy(docId, doc._rev);
|
|
164
|
+
return { id, _deleted: true };
|
|
165
|
+
} catch (error) {
|
|
166
|
+
throw this.parseError(error);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Defining Models
|
|
173
|
+
|
|
174
|
+
Define your data models using the decorators from Decaf.ts:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { model, required, validate } from '@decaf-ts/decorator-validation';
|
|
178
|
+
import { BaseModel, pk, index, table } from '@decaf-ts/core';
|
|
179
|
+
|
|
180
|
+
@table('users')
|
|
181
|
+
@model()
|
|
182
|
+
export class User extends BaseModel {
|
|
183
|
+
@pk()
|
|
184
|
+
id!: string;
|
|
185
|
+
|
|
186
|
+
@required()
|
|
187
|
+
@index()
|
|
188
|
+
email!: string;
|
|
189
|
+
|
|
190
|
+
@required()
|
|
191
|
+
firstName!: string;
|
|
192
|
+
|
|
193
|
+
@required()
|
|
194
|
+
lastName!: string;
|
|
195
|
+
|
|
196
|
+
@index()
|
|
197
|
+
age?: number;
|
|
198
|
+
|
|
199
|
+
constructor(data?: Partial<User>) {
|
|
200
|
+
super(data);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Creating a Repository
|
|
206
|
+
|
|
207
|
+
Create a repository for your model:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { Repository } from '@decaf-ts/core';
|
|
211
|
+
import { CouchDBRepository } from '@decaf-ts/for-couchdb';
|
|
212
|
+
import { User } from './models/User';
|
|
213
|
+
import { MyCouchDBAdapter } from './adapters/MyCouchDBAdapter';
|
|
214
|
+
|
|
215
|
+
// Get the adapter instance
|
|
216
|
+
const adapter = new MyCouchDBAdapter(myScope);
|
|
217
|
+
|
|
218
|
+
// Create a repository for the User model
|
|
219
|
+
const userRepository: CouchDBRepository<User, MyScope, MyFlags, MyContext> =
|
|
220
|
+
Repository.forModel(User, adapter.flavour);
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Basic CRUD Operations
|
|
224
|
+
|
|
225
|
+
Perform basic CRUD operations:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// Create a new user
|
|
229
|
+
const newUser = new User({
|
|
230
|
+
id: '123',
|
|
231
|
+
email: 'john.doe@example.com',
|
|
232
|
+
firstName: 'John',
|
|
233
|
+
lastName: 'Doe',
|
|
234
|
+
age: 30
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Create
|
|
238
|
+
const createdUser = await userRepository.create(newUser);
|
|
239
|
+
|
|
240
|
+
// Read
|
|
241
|
+
const user = await userRepository.read('123');
|
|
242
|
+
|
|
243
|
+
// Update
|
|
244
|
+
user.age = 31;
|
|
245
|
+
const updatedUser = await userRepository.update(user);
|
|
246
|
+
|
|
247
|
+
// Delete
|
|
248
|
+
await userRepository.delete('123');
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Using Statements for Queries
|
|
252
|
+
|
|
253
|
+
Build and execute queries using the Statement builder:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { Condition } from '@decaf-ts/core';
|
|
257
|
+
|
|
258
|
+
// Create a statement
|
|
259
|
+
const statement = adapter.Statement<User>();
|
|
260
|
+
|
|
261
|
+
// Build a query to find users older than 25, sorted by lastName
|
|
262
|
+
const users = await statement
|
|
263
|
+
.from(User)
|
|
264
|
+
.where(Condition.attribute<User>('age').gt(25))
|
|
265
|
+
.orderBy('lastName', 'asc')
|
|
266
|
+
.limit(10)
|
|
267
|
+
.execute<User[]>();
|
|
268
|
+
|
|
269
|
+
// Query with multiple conditions
|
|
270
|
+
const johnDoes = await statement
|
|
271
|
+
.from(User)
|
|
272
|
+
.where(
|
|
273
|
+
Condition.and(
|
|
274
|
+
Condition.attribute<User>('lastName').eq('Doe'),
|
|
275
|
+
Condition.attribute<User>('age').gt(18)
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
.execute<User[]>();
|
|
279
|
+
|
|
280
|
+
// Select specific fields
|
|
281
|
+
const userEmails = await statement
|
|
282
|
+
.from(User)
|
|
283
|
+
.select(['email', 'firstName'])
|
|
284
|
+
.where(Condition.attribute<User>('age').gt(25))
|
|
285
|
+
.execute<Array<Pick<User, 'email' | 'firstName'>>>();
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Pagination
|
|
289
|
+
|
|
290
|
+
Paginate through query results:
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
// Create a paginator
|
|
294
|
+
const paginator = await adapter
|
|
295
|
+
.Statement<User>()
|
|
296
|
+
.from(User)
|
|
297
|
+
.where(Condition.attribute<User>('age').gt(18))
|
|
298
|
+
.orderBy('lastName', 'asc')
|
|
299
|
+
.paginate<User[]>(10); // 10 items per page
|
|
300
|
+
|
|
301
|
+
// Get the first page
|
|
302
|
+
const page1 = await paginator.page(1);
|
|
303
|
+
|
|
304
|
+
// Get the next page
|
|
305
|
+
const page2 = await paginator.page(2);
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Working with Sequences
|
|
309
|
+
|
|
310
|
+
Generate sequential IDs:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { SequenceOptions } from '@decaf-ts/core';
|
|
314
|
+
|
|
315
|
+
// Create a sequence
|
|
316
|
+
const sequenceOptions: SequenceOptions = {
|
|
317
|
+
name: 'user-sequence',
|
|
318
|
+
startWith: 1000,
|
|
319
|
+
incrementBy: 1,
|
|
320
|
+
type: 'Number'
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const sequence = await adapter.Sequence(sequenceOptions);
|
|
324
|
+
|
|
325
|
+
// Get the next value
|
|
326
|
+
const nextId = await sequence.next();
|
|
327
|
+
|
|
328
|
+
// Get a range of values
|
|
329
|
+
const idRange = await sequence.range(5); // Returns 5 sequential IDs
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Index Management
|
|
333
|
+
|
|
334
|
+
Create and manage indexes:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
import { generateIndexDoc } from '@decaf-ts/for-couchdb';
|
|
338
|
+
|
|
339
|
+
// Generate an index configuration
|
|
340
|
+
const indexConfig = generateIndexDoc(
|
|
341
|
+
'email', // attribute
|
|
342
|
+
'users', // tableName
|
|
343
|
+
['firstName'], // compositions
|
|
344
|
+
'asc' // order
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
// Create the index
|
|
348
|
+
await adapter.db.createIndex(indexConfig);
|
|
349
|
+
|
|
350
|
+
// Initialize indexes for all models
|
|
351
|
+
await adapter.initialize();
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Error Handling
|
|
355
|
+
|
|
356
|
+
Handle CouchDB-specific errors:
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
import { IndexError, ConflictError, NotFoundError } from '@decaf-ts/for-couchdb';
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
// Some operation that might fail
|
|
363
|
+
await userRepository.read('non-existent-id');
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (error instanceof NotFoundError) {
|
|
366
|
+
console.error('Document not found:', error.message);
|
|
367
|
+
} else if (error instanceof ConflictError) {
|
|
368
|
+
console.error('Document conflict:', error.message);
|
|
369
|
+
} else if (error instanceof IndexError) {
|
|
370
|
+
console.error('Index error:', error.message);
|
|
371
|
+
} else {
|
|
372
|
+
console.error('Unexpected error:', error);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Raw Mango Queries
|
|
378
|
+
|
|
379
|
+
Execute raw Mango queries:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
import { MangoQuery } from '@decaf-ts/for-couchdb';
|
|
383
|
+
|
|
384
|
+
// Define a raw Mango query
|
|
385
|
+
const rawQuery: MangoQuery = {
|
|
386
|
+
selector: {
|
|
387
|
+
'??table': 'users',
|
|
388
|
+
age: { $gt: 25 },
|
|
389
|
+
lastName: { $eq: 'Doe' }
|
|
390
|
+
},
|
|
391
|
+
fields: ['_id', 'firstName', 'lastName', 'email'],
|
|
392
|
+
sort: [{ lastName: 'asc' }],
|
|
393
|
+
limit: 20
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Execute the raw query
|
|
397
|
+
const results = await adapter.raw(rawQuery, true);
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Utility Functions
|
|
401
|
+
|
|
402
|
+
Use utility functions for common operations:
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import { reAuth, wrapDocumentScope, generateIndexName } from '@decaf-ts/for-couchdb';
|
|
406
|
+
|
|
407
|
+
// Re-authenticate a connection
|
|
408
|
+
await reAuth(connection, 'username', 'password');
|
|
45
409
|
|
|
410
|
+
// Wrap a document scope with automatic re-authentication
|
|
411
|
+
const db = wrapDocumentScope(connection, 'my-database', 'username', 'password');
|
|
412
|
+
|
|
413
|
+
// Generate an index name
|
|
414
|
+
const indexName = generateIndexName('email', 'users', ['firstName'], 'asc');
|
|
415
|
+
```
|
|
46
416
|
|
|
47
417
|
|
|
48
418
|
### Related
|