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