@adobe/aio-lib-db 0.1.0-beta.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -561
- package/lib/DbBase.js +12 -14
- package/lib/constants.js +4 -0
- package/lib/init.js +6 -6
- package/package.json +1 -1
- package/utils/apiRequest.js +2 -7
package/README.md
CHANGED
|
@@ -12,569 +12,11 @@ Install `aio-lib-db` from npm:
|
|
|
12
12
|
npm install @adobe/aio-lib-db
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## Quick Start
|
|
18
|
-
|
|
19
|
-
### Setup
|
|
20
|
-
|
|
21
|
-
**aio-lib-db** is intended to be used by AIO Runtime Actions and the DB Plugin for the AIO CLI, and these are always executed within a specific runtime namespace. Before use, a Workspace Database must be provisioned. (See [Provisioning a Workspace Database](https://developer.adobe.com/app-builder/docs/guides/app_builder_guides/storage/database#provisioning-a-workspace-database) in the [Getting Started with Database Storage](https://developer.adobe.com/app-builder/docs/guides/app_builder_guides/storage/database) guide for details.)
|
|
22
|
-
|
|
23
|
-
**aio-lib-db** must be initialized in the region the workspace database was provisioned. Otherwise, the connection will fail. To explicitly initialize the library in a specific region, pass the `{region: "<region>"}` argument to the `libDb.init()` method. Called with no arguments, `libDb.init()` will initialize the library either in the default `amer` region or in the region defined in the `AIO_DB_REGION` environment variable.
|
|
24
|
-
|
|
25
|
-
### Basic Usage
|
|
26
|
-
|
|
27
|
-
```javascript
|
|
28
|
-
const libDb = require('@adobe/aio-lib-db');
|
|
29
|
-
|
|
30
|
-
async function main() {
|
|
31
|
-
let client;
|
|
32
|
-
try {
|
|
33
|
-
// initialize library with the default amer region or what is defined in AIO_DB_REGION
|
|
34
|
-
const db = await libDb.init();
|
|
35
|
-
|
|
36
|
-
// initialize library with an explicit region
|
|
37
|
-
// const db = await libDb.init({region: "emea"});
|
|
38
|
-
|
|
39
|
-
// connect to the database
|
|
40
|
-
client = await db.connect();
|
|
41
|
-
|
|
42
|
-
// Get a collection
|
|
43
|
-
const users = client.collection('users');
|
|
44
|
-
|
|
45
|
-
// Insert a document
|
|
46
|
-
await users.insertOne({ name: 'John Doe', email: 'john@example.com' });
|
|
47
|
-
|
|
48
|
-
// Find documents
|
|
49
|
-
const cursor = users.find({ name: 'John Doe' });
|
|
50
|
-
const results = await cursor.toArray();
|
|
51
|
-
}
|
|
52
|
-
finally {
|
|
53
|
-
if (client) {
|
|
54
|
-
// Close any open cursors when the application is done
|
|
55
|
-
await client.close();
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## Collection Operations
|
|
63
|
-
|
|
64
|
-
### Insert Operations
|
|
65
|
-
|
|
66
|
-
```javascript
|
|
67
|
-
// Insert a single document
|
|
68
|
-
const result = await collection.insertOne({
|
|
69
|
-
name: 'Jane Smith',
|
|
70
|
-
email: 'jane@example.com',
|
|
71
|
-
age: 30
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Insert multiple documents
|
|
75
|
-
const result = await collection.insertMany([
|
|
76
|
-
{ name: 'Alice', email: 'alice@example.com' },
|
|
77
|
-
{ name: 'Bob', email: 'bob@example.com' }
|
|
78
|
-
]);
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Find Operations
|
|
82
|
-
|
|
83
|
-
```javascript
|
|
84
|
-
// Find one document
|
|
85
|
-
const user = await collection.findOne({ email: 'john@example.com' });
|
|
86
|
-
|
|
87
|
-
// Find all documents matching a filter
|
|
88
|
-
const cursor = collection.find({ age: { $gte: 18 } });
|
|
89
|
-
const adults = await cursor.toArray();
|
|
90
|
-
|
|
91
|
-
// Find with projection and sorting
|
|
92
|
-
const cursor = collection.find({ age: { $gte: 18 } })
|
|
93
|
-
.project({ name: 1, email: 1 })
|
|
94
|
-
.sort({ name: 1 })
|
|
95
|
-
.limit(10);
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Update Operations
|
|
99
|
-
|
|
100
|
-
```javascript
|
|
101
|
-
// Update one document
|
|
102
|
-
const result = await collection.updateOne(
|
|
103
|
-
{ email: 'john@example.com' },
|
|
104
|
-
{ $set: { age: 31 } }
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
// Update multiple documents
|
|
108
|
-
const result = await collection.updateMany(
|
|
109
|
-
{ age: { $lt: 18 } },
|
|
110
|
-
{ $set: { category: 'minor' } }
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
// Find and update
|
|
114
|
-
const updatedUser = await collection.findOneAndUpdate(
|
|
115
|
-
{ email: 'john@example.com' },
|
|
116
|
-
{ $set: { lastLogin: new Date() } },
|
|
117
|
-
{ returnDocument: 'after' }
|
|
118
|
-
);
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Delete Operations
|
|
122
|
-
|
|
123
|
-
```javascript
|
|
124
|
-
// Delete one document
|
|
125
|
-
const result = await collection.deleteOne({ email: 'john@example.com' });
|
|
126
|
-
|
|
127
|
-
// Delete multiple documents
|
|
128
|
-
const result = await collection.deleteMany({ age: { $lt: 0 } });
|
|
129
|
-
|
|
130
|
-
// Find and delete
|
|
131
|
-
const deletedUser = await collection.findOneAndDelete({ email: 'john@example.com' });
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
## Query Building with Cursors
|
|
137
|
-
|
|
138
|
-
> Cursors will close themselves after all results have been processed, but they can be closed early to release resources by calling `cursor.close()`, and best practice is to close them explicitly once they're no longer needed. The `client.close()` method will close all open cursors and connections, so it should be called when the application is shutting down or no longer needs database access.
|
|
139
|
-
|
|
140
|
-
### FindCursor Methods
|
|
141
|
-
|
|
142
|
-
The `find()` method returns a `FindCursor` that supports method chaining:
|
|
143
|
-
|
|
144
|
-
```javascript
|
|
145
|
-
const cursor = collection.find({ status: 'active' })
|
|
146
|
-
.filter({ category: 'premium' }) // Additional filtering
|
|
147
|
-
.sort({ createdAt: -1 }) // Sort by creation date (newest first)
|
|
148
|
-
.project({ name: 1, email: 1, _id: 0 }) // Only include name and email
|
|
149
|
-
.limit(20) // Limit to 20 results
|
|
150
|
-
.skip(10) // Skip first 10 results
|
|
151
|
-
.batchSize(5); // Process in batches of 5
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Cursor Iteration
|
|
155
|
-
|
|
156
|
-
```javascript
|
|
157
|
-
// Using toArray() - loads all results into memory
|
|
158
|
-
const results = await cursor.toArray();
|
|
159
|
-
|
|
160
|
-
// Using iteration - memory efficient
|
|
161
|
-
while (await cursor.hasNext()) {
|
|
162
|
-
const doc = await cursor.next();
|
|
163
|
-
console.log(doc);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Using for await...of - most convenient
|
|
167
|
-
for await (const doc of cursor) {
|
|
168
|
-
console.log(doc);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Using streams
|
|
172
|
-
const stream = cursor.stream();
|
|
173
|
-
stream.on('data', (doc) => {
|
|
174
|
-
console.log(doc);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// Check cursor properties
|
|
178
|
-
console.log('Cursor ID:', cursor.id);
|
|
179
|
-
console.log('Is closed:', cursor.closed);
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Cursor Transformations
|
|
183
|
-
|
|
184
|
-
```javascript
|
|
185
|
-
// Transform documents as they're retrieved
|
|
186
|
-
const cursor = collection.find({ status: 'active' })
|
|
187
|
-
.map(doc => ({
|
|
188
|
-
...doc,
|
|
189
|
-
displayName: `${doc.firstName} ${doc.lastName}`,
|
|
190
|
-
isVip: doc.tier === 'premium'
|
|
191
|
-
}));
|
|
192
|
-
|
|
193
|
-
// Chain multiple transformations
|
|
194
|
-
const cursor = collection.find({ status: 'active' })
|
|
195
|
-
.map(doc => ({ ...doc, processed: true }))
|
|
196
|
-
.map(doc => ({ ...doc, timestamp: new Date() }));
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
## Aggregation Pipeline
|
|
202
|
-
|
|
203
|
-
### Basic Aggregation
|
|
204
|
-
|
|
205
|
-
```javascript
|
|
206
|
-
// Simple aggregation
|
|
207
|
-
const pipeline = [
|
|
208
|
-
{ $match: { status: 'active' } },
|
|
209
|
-
{ $group: { _id: '$category', count: { $sum: 1 } } },
|
|
210
|
-
{ $sort: { count: -1 } }
|
|
211
|
-
];
|
|
212
|
-
|
|
213
|
-
const cursor = collection.aggregate(pipeline);
|
|
214
|
-
const results = await cursor.toArray();
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### Chained Aggregation Building
|
|
218
|
-
|
|
219
|
-
```javascript
|
|
220
|
-
// Build aggregation pipeline using method chaining
|
|
221
|
-
const cursor = collection.aggregate()
|
|
222
|
-
.match({ status: 'active' })
|
|
223
|
-
.group({ _id: '$category', total: { $sum: '$amount' } })
|
|
224
|
-
.sort({ total: -1 })
|
|
225
|
-
.limit(10)
|
|
226
|
-
.project({ category: '$_id', total: 1, _id: 0 });
|
|
227
|
-
|
|
228
|
-
const topCategories = await cursor.toArray();
|
|
229
|
-
|
|
230
|
-
// Geospatial aggregation example
|
|
231
|
-
const nearbyStores = await collection.aggregate()
|
|
232
|
-
.geoNear({
|
|
233
|
-
near: { type: 'Point', coordinates: [-122.4194, 37.7749] }, // San Francisco
|
|
234
|
-
distanceField: 'distance',
|
|
235
|
-
maxDistance: 1000, // 1km radius
|
|
236
|
-
spherical: true
|
|
237
|
-
})
|
|
238
|
-
.match({ status: 'open' })
|
|
239
|
-
.limit(10)
|
|
240
|
-
.toArray();
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
### Advanced Aggregation
|
|
244
|
-
|
|
245
|
-
```javascript
|
|
246
|
-
// Complex aggregation with multiple stages
|
|
247
|
-
const cursor = collection.aggregate()
|
|
248
|
-
.match({ dateCreated: { $gte: new Date('2024-01-01') } })
|
|
249
|
-
.lookup({
|
|
250
|
-
from: 'categories',
|
|
251
|
-
localField: 'categoryId',
|
|
252
|
-
foreignField: '_id',
|
|
253
|
-
as: 'category'
|
|
254
|
-
})
|
|
255
|
-
.unwind('$category')
|
|
256
|
-
.redact({
|
|
257
|
-
$cond: {
|
|
258
|
-
if: { $eq: ['$category.status', 'active'] },
|
|
259
|
-
then: '$$DESCEND',
|
|
260
|
-
else: '$$PRUNE'
|
|
261
|
-
}
|
|
262
|
-
})
|
|
263
|
-
.group({
|
|
264
|
-
_id: '$category.name',
|
|
265
|
-
totalSales: { $sum: '$amount' },
|
|
266
|
-
averageOrder: { $avg: '$amount' },
|
|
267
|
-
orderCount: { $sum: 1 }
|
|
268
|
-
})
|
|
269
|
-
.sort({ totalSales: -1 })
|
|
270
|
-
.limit(5)
|
|
271
|
-
.out('sales_summary'); // Output results to a new collection
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
---
|
|
275
|
-
|
|
276
|
-
## Advanced Features
|
|
277
|
-
|
|
278
|
-
### Storage Statics
|
|
279
|
-
|
|
280
|
-
#### Individual database statistics:
|
|
281
|
-
|
|
282
|
-
```javascript
|
|
283
|
-
// Get storage statistics for the database with the default scale factor (bytes)
|
|
284
|
-
const dbStats = client.dbStats()
|
|
285
|
-
|
|
286
|
-
// Get storage statistics for the database with a scale factor (e.g. KB)
|
|
287
|
-
const dbStatsKb = client.dbStats({ scale: 1024 })
|
|
288
|
-
```
|
|
289
|
-
| field returned | description |
|
|
290
|
-
|----------------|-------------------------------------------------------------------------------------------------|
|
|
291
|
-
| collections | the number of collections |
|
|
292
|
-
| objects | the number of objects/documents |
|
|
293
|
-
| views | the number of views (not currently supported) |
|
|
294
|
-
| indexes | the number of indexes |
|
|
295
|
-
| dataSize | the actual amount of storage used (default bytes) |
|
|
296
|
-
| storageSize | space allocated for storage (default bytes) |
|
|
297
|
-
| indexSize | space allocated for indexes (default bytes) |
|
|
298
|
-
| ok | whether the request was successful |
|
|
299
|
-
| scaleFactor | the scale factor used for the size fields, ex: 1024 for kilobyte-scale (default is 1 for bytes) |
|
|
300
|
-
| lastUpdated | when the statistics were last updated |
|
|
301
|
-
|
|
302
|
-
#### Organization storage statistics:
|
|
303
|
-
|
|
304
|
-
```javascript
|
|
305
|
-
// Get combined storage statistics across databases in the organization with the default scale factor (bytes)
|
|
306
|
-
const orgStats = client.orgStats()
|
|
307
|
-
|
|
308
|
-
// Get combined storage statistics across databases in the organization with a scale factor (e.g. MB)
|
|
309
|
-
const orgStatsMb = client.orgStats({ scale: 1024 * 1024 })
|
|
310
|
-
```
|
|
311
|
-
| field returned | description |
|
|
312
|
-
|---------------------------|----------------------------------------------------------------------------------------------------------------------------|
|
|
313
|
-
| ok | whether the request was successful |
|
|
314
|
-
| databases | the number of databases in the organization |
|
|
315
|
-
| collections | the total number of collections across databases |
|
|
316
|
-
| dataSize | the total actual amount of storage used across databases (default bytes) |
|
|
317
|
-
| storageSize | space allocated for storage (default bytes) |
|
|
318
|
-
| indexSize | space allocated for indexes (default bytes) |
|
|
319
|
-
| scaleFactor | the scale factor used for the size fields, ex: 1024 for kilobyte-scale (default is 1 for bytes) |
|
|
320
|
-
| databaseStats | an array of statistics for individual databases in the organization |
|
|
321
|
-
| databaseStats.namespace | the runtime namespace the database corresponds to |
|
|
322
|
-
| databaseStats.dataSize | the actual amount of storage used by the database (default bytes) |
|
|
323
|
-
| databaseStats.storageSize | space allocated for storage for the database (default bytes) |
|
|
324
|
-
| databaseStats.indexSize | space allocated for indexes for the database (default bytes) |
|
|
325
|
-
| databaseStats.collections | the number of collections in the database |
|
|
326
|
-
| databaseStats.scaleFactor | the scale factor used for the size fields in the databaseStats array, ex: 1024 for kilobyte-scale (default is 1 for bytes) |
|
|
327
|
-
| databaseStats.lastUpdated | when the database statistics were last updated |
|
|
328
|
-
|
|
329
|
-
### Indexing
|
|
330
|
-
|
|
331
|
-
```javascript
|
|
332
|
-
// Create indexes for better query performance
|
|
333
|
-
await collection.createIndex({ email: 1 }, { unique: true });
|
|
334
|
-
await collection.createIndex({ 'profile.age': 1, status: 1 });
|
|
335
|
-
|
|
336
|
-
// List all indexes
|
|
337
|
-
const indexes = await collection.getIndexes();
|
|
338
|
-
|
|
339
|
-
// Drop an index
|
|
340
|
-
await collection.dropIndex('email_1');
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### Counting Documents
|
|
344
|
-
|
|
345
|
-
```javascript
|
|
346
|
-
// Fast count estimate (uses collection metadata)
|
|
347
|
-
const estimate = await collection.estimatedDocumentCount();
|
|
348
|
-
|
|
349
|
-
// Accurate count with filter (scans documents)
|
|
350
|
-
const activeUsers = await collection.countDocuments({ status: 'active' });
|
|
351
|
-
|
|
352
|
-
// Count all documents accurately
|
|
353
|
-
const totalExact = await collection.countDocuments({});
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### Bulk Operations
|
|
357
|
-
|
|
358
|
-
```javascript
|
|
359
|
-
// Perform multiple operations in a single request
|
|
360
|
-
const operations = [
|
|
361
|
-
{ insertOne: { document: { name: 'Alice' } } },
|
|
362
|
-
{ updateOne: { filter: { name: 'Bob' }, update: { $set: { age: 30 } } } },
|
|
363
|
-
{ deleteOne: { filter: { name: 'Charlie' } } }
|
|
364
|
-
];
|
|
365
|
-
|
|
366
|
-
const result = await collection.bulkWrite(operations);
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
### Collection Management
|
|
370
|
-
|
|
371
|
-
```javascript
|
|
372
|
-
// Drop a collection (permanently delete)
|
|
373
|
-
await collection.drop();
|
|
374
|
-
|
|
375
|
-
// Rename a collection
|
|
376
|
-
await collection.renameCollection('new_collection_name');
|
|
377
|
-
|
|
378
|
-
// Create a new collection with options
|
|
379
|
-
const newCollection = await client.createCollection('analytics', {
|
|
380
|
-
validator: {
|
|
381
|
-
$jsonSchema: {
|
|
382
|
-
required: ['userId', 'action', 'timestamp'],
|
|
383
|
-
properties: {
|
|
384
|
-
userId: { type: 'string' },
|
|
385
|
-
action: { type: 'string' },
|
|
386
|
-
timestamp: { type: 'date' }
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
### Query Options
|
|
394
|
-
|
|
395
|
-
```javascript
|
|
396
|
-
// Advanced query options
|
|
397
|
-
const cursor = collection.find({ status: 'active' })
|
|
398
|
-
.hint({ status: 1 }) // Use specific index
|
|
399
|
-
.maxTimeMS(5000) // Set query timeout
|
|
400
|
-
.readConcern({ level: 'majority' }) // Set read concern
|
|
401
|
-
.collation({ locale: 'en', strength: 2 }) // Case-insensitive sorting
|
|
402
|
-
.noCursorTimeout(true); // Disable cursor timeout
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
---
|
|
406
|
-
|
|
407
|
-
## Error Handling
|
|
408
|
-
|
|
409
|
-
```javascript
|
|
410
|
-
try {
|
|
411
|
-
await collection.insertOne({ email: 'invalid-email' });
|
|
412
|
-
} catch (error) {
|
|
413
|
-
if (error.name == 'DbError') {
|
|
414
|
-
console.error('Database error:', error.message);
|
|
415
|
-
} else {
|
|
416
|
-
console.error('Unexpected error:', error);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
---
|
|
422
|
-
|
|
423
|
-
## Best Practices
|
|
424
|
-
|
|
425
|
-
### 1. **Always Close Connections**
|
|
426
|
-
|
|
427
|
-
```javascript
|
|
428
|
-
const client = await db.connect();
|
|
429
|
-
try {
|
|
430
|
-
// Your database operations
|
|
431
|
-
} finally {
|
|
432
|
-
await client.close();
|
|
433
|
-
}
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
### 2. **Use Projections for Large Documents**
|
|
437
|
-
|
|
438
|
-
```javascript
|
|
439
|
-
// Only fetch needed fields
|
|
440
|
-
const users = await collection.find({})
|
|
441
|
-
.project({ name: 1, email: 1, _id: 0 })
|
|
442
|
-
.toArray();
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
### 3. **Use Indexes for Frequent Queries**
|
|
446
|
-
|
|
447
|
-
```javascript
|
|
448
|
-
// Create indexes for frequently queried fields
|
|
449
|
-
await collection.createIndex({ email: 1 });
|
|
450
|
-
await collection.createIndex({ status: 1, createdAt: -1 });
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
### 4. **Handle Large Result Sets with Cursors**
|
|
454
|
-
|
|
455
|
-
```javascript
|
|
456
|
-
// For large datasets, use cursor iteration instead of toArray()
|
|
457
|
-
for await (const doc of collection.find({})) {
|
|
458
|
-
// Process one document at a time
|
|
459
|
-
await processDocument(doc);
|
|
460
|
-
}
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
### 5. **Use Aggregation for Complex Queries**
|
|
464
|
-
|
|
465
|
-
```javascript
|
|
466
|
-
// Use aggregation for complex data processing
|
|
467
|
-
const report = await collection.aggregate()
|
|
468
|
-
.match({ date: { $gte: startDate } })
|
|
469
|
-
.group({ _id: '$category', total: { $sum: '$amount' } })
|
|
470
|
-
.sort({ total: -1 })
|
|
471
|
-
.toArray();
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
---
|
|
475
|
-
|
|
476
|
-
## API Reference
|
|
477
|
-
|
|
478
|
-
### DbClient
|
|
479
|
-
|
|
480
|
-
- `dbStats()` - Get database statistics
|
|
481
|
-
- `listCollections(filter?, options?)` - List collections
|
|
482
|
-
- `collection(name)` - Get collection instance
|
|
483
|
-
- `createCollection(name, options?)` - Create new collection
|
|
484
|
-
- `close()` - Close the connection and all open cursors
|
|
485
|
-
|
|
486
|
-
### DbCollection
|
|
487
|
-
|
|
488
|
-
**Insert Operations:**
|
|
489
|
-
- `insertOne(document, options?)` - Insert single document
|
|
490
|
-
- `insertMany(documents, options?)` - Insert multiple documents
|
|
491
|
-
|
|
492
|
-
**Find Operations:**
|
|
493
|
-
- `findOne(filter, options?)` - Find single document
|
|
494
|
-
- `find(filter?, options?)` - Find multiple documents (returns FindCursor)
|
|
495
|
-
- `findArray(filter?, options?)` - Find single batch as array
|
|
496
|
-
|
|
497
|
-
**Update Operations:**
|
|
498
|
-
- `updateOne(filter, update, options?)` - Update single document
|
|
499
|
-
- `updateMany(filter, update, options?)` - Update multiple documents
|
|
500
|
-
- `findOneAndUpdate(filter, update, options?)` - Find and update
|
|
501
|
-
- `replaceOne(filter, replacement, options?)` - Replace document
|
|
502
|
-
|
|
503
|
-
**Delete Operations:**
|
|
504
|
-
- `deleteOne(filter, options?)` - Delete single document
|
|
505
|
-
- `deleteMany(filter, options?)` - Delete multiple documents
|
|
506
|
-
- `findOneAndDelete(filter, options?)` - Find and delete
|
|
507
|
-
|
|
508
|
-
**Aggregation:**
|
|
509
|
-
- `aggregate(pipeline?, options?)` - Run aggregation (returns AggregateCursor)
|
|
510
|
-
|
|
511
|
-
**Utility Operations:**
|
|
512
|
-
- `countDocuments(filter?, options?)` - Count documents
|
|
513
|
-
- `estimatedDocumentCount(options?)` - Estimate document count from metadata
|
|
514
|
-
- `distinct(field, filter?, options?)` - Get distinct values
|
|
515
|
-
- `bulkWrite(operations, options?)` - Bulk operations
|
|
516
|
-
|
|
517
|
-
**Statistics & Monitoring:**
|
|
518
|
-
- `stats(options?)` - Get collection statistics
|
|
519
|
-
|
|
520
|
-
**Index Operations:**
|
|
521
|
-
- `createIndex(specification, options?)` - Create index
|
|
522
|
-
- `getIndexes()` - List indexes
|
|
523
|
-
- `dropIndex(indexName, options?)` - Drop index
|
|
524
|
-
|
|
525
|
-
**Collection Management:**
|
|
526
|
-
- `drop(options?)` - Drop the collection
|
|
527
|
-
- `renameCollection(newName, options?)` - Rename collection
|
|
528
|
-
|
|
529
|
-
### FindCursor
|
|
530
|
-
|
|
531
|
-
**Query Building:**
|
|
532
|
-
- `filter(filter)` - Set query filter
|
|
533
|
-
- `sort(sort, direction?)` - Set sort order
|
|
534
|
-
- `project(projection)` - Set field projection
|
|
535
|
-
- `limit(limit)` - Set result limit
|
|
536
|
-
- `skip(skip)` - Set number to skip
|
|
537
|
-
- `batchSize(size)` - Set batch size
|
|
538
|
-
|
|
539
|
-
**Iteration:**
|
|
540
|
-
- `hasNext()` - Check if more results available
|
|
541
|
-
- `next()` - Get next document
|
|
542
|
-
- `toArray()` - Get all results as array
|
|
543
|
-
|
|
544
|
-
**Properties:**
|
|
545
|
-
- `id` - Get cursor ID
|
|
546
|
-
- `closed` - Check if cursor is closed and exhausted
|
|
547
|
-
|
|
548
|
-
**Utilities:**
|
|
549
|
-
- `map(transform)` - Transform documents
|
|
550
|
-
- `stream(transform?)` - Get readable stream
|
|
551
|
-
- `close()` - Close the cursor and release resources
|
|
552
|
-
|
|
553
|
-
### AggregateCursor
|
|
554
|
-
|
|
555
|
-
**Pipeline Building:**
|
|
556
|
-
- `match(filter)` - Add $match stage
|
|
557
|
-
- `group(groupSpec)` - Add $group stage
|
|
558
|
-
- `sort(sort)` - Add $sort stage
|
|
559
|
-
- `project(projection)` - Add $project stage
|
|
560
|
-
- `limit(limit)` - Add $limit stage
|
|
561
|
-
- `skip(skip)` - Add $skip stage
|
|
562
|
-
- `lookup(lookupSpec)` - Add $lookup stage
|
|
563
|
-
- `unwind(path)` - Add $unwind stage
|
|
564
|
-
- `out(outSpec)` - Add $out stage (output to collection)
|
|
565
|
-
- `redact(redactSpec)` - Add $redact stage (conditional filtering)
|
|
566
|
-
- `geoNear(geoNearSpec)` - Add $geoNear stage (geospatial queries)
|
|
567
|
-
- `addStage(stage)` - Add custom stage
|
|
568
|
-
|
|
569
|
-
**Iteration:** (Same as FindCursor)
|
|
570
|
-
- `hasNext()`, `next()`, `toArray()`, `stream()`, etc.
|
|
15
|
+
## Documentation
|
|
571
16
|
|
|
572
|
-
|
|
573
|
-
- `id` - Get cursor ID
|
|
574
|
-
- `closed` - Check if cursor is closed and exhausted
|
|
17
|
+
Main documentation for App Builder Database Storage:
|
|
575
18
|
|
|
576
|
-
|
|
577
|
-
- `close()` - Close the cursor and release resources
|
|
19
|
+
* [Getting Started with Database Storage](https://developer.adobe.com/app-builder/docs/guides/app_builder_guides/storage/database)
|
|
578
20
|
|
|
579
21
|
---
|
|
580
22
|
|
package/lib/DbBase.js
CHANGED
|
@@ -24,23 +24,21 @@ class DbBase {
|
|
|
24
24
|
/**
|
|
25
25
|
* @param {string} region
|
|
26
26
|
* @param {string} runtimeNamespace
|
|
27
|
-
* @param {string}
|
|
27
|
+
* @param {string} token
|
|
28
28
|
* @hideconstructor
|
|
29
29
|
*/
|
|
30
|
-
constructor(region, runtimeNamespace,
|
|
31
|
-
this.runtimeAuth = runtimeAuth
|
|
32
|
-
if (!this.runtimeAuth) {
|
|
33
|
-
throw new DbError('Runtime auth is required')
|
|
34
|
-
}
|
|
35
|
-
if (!/.+:.+/.test(this.runtimeAuth)) {
|
|
36
|
-
throw new DbError("Invalid format for runtime auth, must be '<user>:<pass>'")
|
|
37
|
-
}
|
|
30
|
+
constructor(region, runtimeNamespace, token) {
|
|
38
31
|
|
|
39
32
|
this.runtimeNamespace = runtimeNamespace
|
|
40
33
|
if (!this.runtimeNamespace) {
|
|
41
34
|
throw new DbError('Runtime namespace is required')
|
|
42
35
|
}
|
|
43
36
|
|
|
37
|
+
this.accessToken = token
|
|
38
|
+
if (!this.accessToken) {
|
|
39
|
+
throw new DbError('Ims access token is required')
|
|
40
|
+
}
|
|
41
|
+
|
|
44
42
|
this.region = region.toLowerCase()
|
|
45
43
|
const env = process.env.AIO_DB_ENVIRONMENT || getCliEnv()
|
|
46
44
|
const validRegions = ALLOWED_REGIONS[env]
|
|
@@ -78,16 +76,15 @@ class DbBase {
|
|
|
78
76
|
*
|
|
79
77
|
* @static
|
|
80
78
|
* @constructs
|
|
81
|
-
* @param {Object
|
|
79
|
+
* @param {Object} config
|
|
82
80
|
* @param {string=} config.namespace required here or as __OW_API_NAMESPACE in .env
|
|
83
|
-
* @param {string=} config.apikey required here or as __OW_API_KEY in .env
|
|
84
81
|
* @param {string=} config.region optional, default is 'amer'. Allowed prod values are 'amer', 'emea', 'apac', 'aus'
|
|
82
|
+
* @param {string} config.token required to be passed here for Ims authentication
|
|
85
83
|
* @returns {Promise<DbBase>} a new DbBase instance
|
|
86
84
|
* @memberof DbBase
|
|
87
85
|
*/
|
|
88
|
-
static async init(config
|
|
86
|
+
static async init(config) {
|
|
89
87
|
const namespace = config.namespace || process.env.__OW_NAMESPACE
|
|
90
|
-
const apikey = config.apikey || process.env.__OW_API_KEY
|
|
91
88
|
let aioAppRegion = null
|
|
92
89
|
|
|
93
90
|
try {
|
|
@@ -97,7 +94,8 @@ class DbBase {
|
|
|
97
94
|
}
|
|
98
95
|
|
|
99
96
|
const region = aioAppRegion || config.region || ALLOWED_REGIONS[getCliEnv()].at(0)
|
|
100
|
-
|
|
97
|
+
const token = config.token
|
|
98
|
+
return new DbBase(region, namespace, token)
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
/**
|
package/lib/constants.js
CHANGED
|
@@ -13,6 +13,8 @@ require('dotenv/config')
|
|
|
13
13
|
|
|
14
14
|
const RUNTIME_HEADER = 'x-runtime-namespace'
|
|
15
15
|
const REQUEST_ID_HEADER = 'x-request-id'
|
|
16
|
+
const IMS_AUTHORIZATION_HEADER = 'Authorization'
|
|
17
|
+
const IMS_AUTHORIZATION_HEADER_PREFIX = 'Bearer '
|
|
16
18
|
|
|
17
19
|
const CURSOR_INIT_ERR_MESSAGE = 'Cursor has already been initialized, cannot modify request.'
|
|
18
20
|
|
|
@@ -34,6 +36,8 @@ const PROD_ENDPOINT_EXTERNAL = 'https://storage-database-<region>.app-builder.ad
|
|
|
34
36
|
module.exports = {
|
|
35
37
|
RUNTIME_HEADER,
|
|
36
38
|
REQUEST_ID_HEADER,
|
|
39
|
+
IMS_AUTHORIZATION_HEADER,
|
|
40
|
+
IMS_AUTHORIZATION_HEADER_PREFIX,
|
|
37
41
|
CURSOR_INIT_ERR_MESSAGE,
|
|
38
42
|
PROD_ENV,
|
|
39
43
|
STAGE_ENV,
|
package/lib/init.js
CHANGED
|
@@ -18,18 +18,18 @@ require('dotenv/config')
|
|
|
18
18
|
* Initializes and returns the ADP Storage Database SDK.
|
|
19
19
|
*
|
|
20
20
|
* To use the SDK you must either provide your OpenWhisk credentials in `config.ow` or in the environment variables
|
|
21
|
-
* `__OW_NAMESPACE
|
|
21
|
+
* `__OW_NAMESPACE`.
|
|
22
22
|
*
|
|
23
|
-
* @param {Object
|
|
23
|
+
* @param {Object} config used to init the sdk
|
|
24
24
|
* @param {('amer'|'apac'|'emea'|'aus')=} [config.region] optional region to use, default: `amer`
|
|
25
25
|
* @param {Object=} [config.ow] Set those if you want to use ootb credentials to access the database service
|
|
26
26
|
* @param {string=} [config.ow.namespace]
|
|
27
|
-
* @param {string
|
|
27
|
+
* @param {string} config.token Ims acccess token required here to authenticate
|
|
28
28
|
* @returns {Promise<DbBase>} A DbBase instance
|
|
29
29
|
*/
|
|
30
|
-
async function init (config
|
|
31
|
-
const {
|
|
32
|
-
return DbBase.init({
|
|
30
|
+
async function init (config) {
|
|
31
|
+
const { namespace } = (config.ow ?? {})
|
|
32
|
+
return DbBase.init({ namespace, region: config.region, token: config.token })
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
module.exports = { init }
|
package/package.json
CHANGED
package/utils/apiRequest.js
CHANGED
|
@@ -9,7 +9,7 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
|
|
|
9
9
|
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
const { RUNTIME_HEADER, REQUEST_ID_HEADER } = require('../lib/constants')
|
|
12
|
+
const { RUNTIME_HEADER, REQUEST_ID_HEADER, IMS_AUTHORIZATION_HEADER, IMS_AUTHORIZATION_HEADER_PREFIX } = require('../lib/constants')
|
|
13
13
|
const DbError = require('../lib/DbError')
|
|
14
14
|
const { EJSON } = require("bson")
|
|
15
15
|
|
|
@@ -60,15 +60,10 @@ async function apiRequest(db, axiosClient, apiPath, method, body = {}) {
|
|
|
60
60
|
const fullUrl = `${db.serviceUrl}/${apiPath}`
|
|
61
61
|
let res
|
|
62
62
|
try {
|
|
63
|
-
const creds = db.runtimeAuth.split(/:(.*)/,2)
|
|
64
63
|
/** @type {Object}
|
|
65
64
|
* @mixes AxiosRequestConfig */
|
|
66
65
|
const reqConfig = {
|
|
67
|
-
headers: { [RUNTIME_HEADER]: db.runtimeNamespace }
|
|
68
|
-
auth: {
|
|
69
|
-
username: creds[0],
|
|
70
|
-
password: creds[1]
|
|
71
|
-
}
|
|
66
|
+
headers: { [RUNTIME_HEADER]: db.runtimeNamespace, [IMS_AUTHORIZATION_HEADER]: IMS_AUTHORIZATION_HEADER_PREFIX + db.accessToken }
|
|
72
67
|
}
|
|
73
68
|
if (method === 'GET') {
|
|
74
69
|
res = await axiosClient.get(fullUrl, reqConfig)
|