Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,47 @@ npm install @adobe/aio-lib-db

**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.

**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.

```bash
npm install @adobe/aio-sdk --save
```

To Add IMS credentials in your Runtime action parameter, set the action annotation `include-ims-credentials: true` in AIO App `app.config.yaml` file.
Comment thread
nofuss marked this conversation as resolved.

```yaml
actions:
action:
function: actions/generic/action.js
annotations:
include-ims-credentials: true
require-adobe-auth: true
final: true
```

> [!IMPORTANT]
> 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.)

### Basic Usage

```javascript
const { generateAccessToken } = require('@adobe/aio-sdk').Core.AuthClient;
const libDb = require('@adobe/aio-lib-db');

async function main() {
// Runtime action params
async function main(params) {
let client;
try {
// initialize library with the default amer region or what is defined in AIO_DB_REGION
const db = await libDb.init();
// Generate access token
const token = await generateAccessToken(params);

// Initialize library with token
const db = await libDb.init({ token: token });

// initialize library with an explicit region
// const db = await libDb.init({region: "emea"});
// or with explicit region, the default being amer or whatever is defined in AIO_DB_REGION
// const db = await libDb.init({ token: token, region: 'emea' });

// connect to the database
// Connect to the database
client = await db.connect();

// Get a collection
Expand Down
26 changes: 12 additions & 14 deletions lib/DbBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,21 @@ class DbBase {
/**
* @param {string} region
* @param {string} runtimeNamespace
* @param {string} runtimeAuth
* @param {string} token
* @hideconstructor
*/
constructor(region, runtimeNamespace, runtimeAuth) {
this.runtimeAuth = runtimeAuth
if (!this.runtimeAuth) {
throw new DbError('Runtime auth is required')
}
if (!/.+:.+/.test(this.runtimeAuth)) {
throw new DbError("Invalid format for runtime auth, must be '<user>:<pass>'")
}
constructor(region, runtimeNamespace, token) {

this.runtimeNamespace = runtimeNamespace
if (!this.runtimeNamespace) {
throw new DbError('Runtime namespace is required')
}

this.accessToken = token
if (!this.accessToken) {
throw new DbError('Ims access token is required')
}

this.region = region.toLowerCase()
const env = process.env.AIO_DB_ENVIRONMENT || getCliEnv()
const validRegions = ALLOWED_REGIONS[env]
Expand Down Expand Up @@ -78,16 +76,15 @@ class DbBase {
*
* @static
* @constructs
* @param {Object=} config
* @param {Object} config
* @param {string=} config.namespace required here or as __OW_API_NAMESPACE in .env
* @param {string=} config.apikey required here or as __OW_API_KEY in .env
* @param {string=} config.region optional, default is 'amer'. Allowed prod values are 'amer', 'emea', 'apac', 'aus'
* @param {string} config.token required to be passed here for Ims authentication
* @returns {Promise<DbBase>} a new DbBase instance
* @memberof DbBase
*/
static async init(config = {}) {
static async init(config) {
const namespace = config.namespace || process.env.__OW_NAMESPACE
const apikey = config.apikey || process.env.__OW_API_KEY
let aioAppRegion = null

try {
Expand All @@ -97,7 +94,8 @@ class DbBase {
}

const region = aioAppRegion || config.region || ALLOWED_REGIONS[getCliEnv()].at(0)
return new DbBase(region, namespace, apikey)
const token = config.token
return new DbBase(region, namespace, token)
}

/**
Expand Down
4 changes: 4 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ require('dotenv/config')

const RUNTIME_HEADER = 'x-runtime-namespace'
const REQUEST_ID_HEADER = 'x-request-id'
const IMS_AUTHORIZATION_HEADER = 'Authorization'
const IMS_AUTHORIZATION_HEADER_PREFIX = 'Bearer '

const CURSOR_INIT_ERR_MESSAGE = 'Cursor has already been initialized, cannot modify request.'

Expand All @@ -34,6 +36,8 @@ const PROD_ENDPOINT_EXTERNAL = 'https://storage-database-<region>.app-builder.ad
module.exports = {
RUNTIME_HEADER,
REQUEST_ID_HEADER,
IMS_AUTHORIZATION_HEADER,
IMS_AUTHORIZATION_HEADER_PREFIX,
CURSOR_INIT_ERR_MESSAGE,
PROD_ENV,
STAGE_ENV,
Expand Down
12 changes: 6 additions & 6 deletions lib/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ require('dotenv/config')
* Initializes and returns the ADP Storage Database SDK.
*
* To use the SDK you must either provide your OpenWhisk credentials in `config.ow` or in the environment variables
* `__OW_NAMESPACE` and `__OW_API_KEY`.
* `__OW_NAMESPACE`.
*
* @param {Object=} [config] used to init the sdk
* @param {Object} config used to init the sdk
* @param {('amer'|'apac'|'emea'|'aus')=} [config.region] optional region to use, default: `amer`
* @param {Object=} [config.ow] Set those if you want to use ootb credentials to access the database service
* @param {string=} [config.ow.namespace]
* @param {string=} [config.ow.auth]
* @param {string} config.token Ims acccess token required here to authenticate
* @returns {Promise<DbBase>} A DbBase instance
*/
async function init (config = {}) {
const { auth: apikey, namespace } = (config.ow ?? {})
return DbBase.init({ apikey, namespace, region: config.region })
async function init (config) {
const { namespace } = (config.ow ?? {})
return DbBase.init({ namespace, region: config.region, token: config.token })
}

module.exports = { init }
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/aio-lib-db",
"version": "0.1.0-beta.8",
"version": "0.2.0-beta.1",
"description": "An abstraction on top of Document DB storage",
"main": "index.js",
"scripts": {
Expand Down
54 changes: 27 additions & 27 deletions tests/lib/DbBase.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
const { getDb, TEST_NAMESPACE, TEST_AUTH } = require("../testingUtils")
const { getDb, TEST_NAMESPACE, TEST_ACCESS_TOKEN } = require("../testingUtils")
const DbBase = require("../../lib/DbBase")
const {
PROD_ENV, STAGE_ENV, ALLOWED_REGIONS, PROD_ENDPOINT_EXTERNAL, STAGE_ENDPOINT, PROD_ENDPOINT_RUNTIME
Expand Down Expand Up @@ -87,7 +87,7 @@ describe('DbBase tests', () => {

for (const region of regions) {
const expectedUrl = url.replaceAll(/<region>/gi, region)
const dbInstance = await DbBase.init({ region: region, namespace: TEST_NAMESPACE, apikey: TEST_AUTH })
const dbInstance = await DbBase.init({ region: region, namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN })
const axiosClient = dbInstance.axiosClient
expect(dbInstance.serviceUrl).toBe(expectedUrl)
await apiGet(dbInstance, axiosClient, 'db/ping')
Expand All @@ -99,7 +99,7 @@ describe('DbBase tests', () => {
// Test default region
const defaultRegion = regions.at(0)
const expectedUrl = url.replaceAll(/<region>/gi, defaultRegion)
const defaultDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH })
const defaultDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN })
const axiosClient = defaultDb.axiosClient
expect(defaultDb.serviceUrl).toBe(expectedUrl)
await apiGet(defaultDb, axiosClient, 'db/ping')
Expand All @@ -116,7 +116,7 @@ describe('DbBase tests', () => {

// Test that override applies regardless of the execution context
// Default (External)
const dbCustomExternal = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH })
const dbCustomExternal = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN })
const externalAxios = dbCustomExternal.axiosClient
expect(dbCustomExternal.serviceUrl).toBe(customEndpoint)
await apiGet(dbCustomExternal, externalAxios, 'db/ping')
Expand All @@ -126,7 +126,7 @@ describe('DbBase tests', () => {

// Runtime
process.env.__OW_ACTIVATION_ID = 'some-activation-id'
const dbCustomRuntime = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH })
const dbCustomRuntime = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN })
const runtimeAxios = dbCustomRuntime.axiosClient
expect(dbCustomRuntime.serviceUrl).toBe(customEndpoint)
await apiGet(dbCustomRuntime, runtimeAxios, 'db/ping')
Expand All @@ -152,11 +152,11 @@ describe('DbBase tests', () => {
]

for (const r of allowed) {
await expect(DbBase.init({ region: r, namespace: TEST_NAMESPACE, apikey: TEST_AUTH })).resolves
await expect(DbBase.init({ region: r, namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN })).resolves
}
for (const r of unsupported) {
await expect(
DbBase.init({ region: r, namespace: TEST_NAMESPACE, apikey: TEST_AUTH })
DbBase.init({ region: r, namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN })
).rejects.toThrow(`Invalid region '${r}'`)
}
})
Expand All @@ -169,23 +169,23 @@ describe('DbBase tests', () => {
// Set AIO_DB_ENVIRONMENT to prod and getCliEnv() to stage, expect prod
process.env.AIO_DB_ENVIRONMENT = PROD_ENV
getCliEnv.mockReturnValue(STAGE_ENV)
let dbInstance = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
let dbInstance = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(dbInstance.serviceUrl).toBe(prodUrl)

// Set AIO_DB_ENVIRONMENT to stage and getCliEnv() to prod, expect stage
process.env.AIO_DB_ENVIRONMENT = STAGE_ENV
getCliEnv.mockReturnValue(PROD_ENV)
dbInstance = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
dbInstance = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(dbInstance.serviceUrl).toBe(stageUrl)

// Remove AIO_DB_ENVIRONMENT, expect getCliEnv() to take precedence
delete process.env.AIO_DB_ENVIRONMENT
getCliEnv.mockReturnValue(PROD_ENV)
dbInstance = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
dbInstance = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(dbInstance.serviceUrl).toBe(prodUrl)

getCliEnv.mockReturnValue(STAGE_ENV)
dbInstance = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
dbInstance = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(dbInstance.serviceUrl).toBe(stageUrl)
})

Expand All @@ -201,30 +201,30 @@ describe('DbBase tests', () => {
delete process.env.AIO_DEV

process.env.AIO_DB_ENVIRONMENT = STAGE_ENV
const runtimeStageDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const runtimeStageDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(runtimeStageDb.serviceUrl).toBe(stageUrl)

process.env.AIO_DB_ENVIRONMENT = PROD_ENV
const runtimeProdDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const runtimeProdDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(runtimeProdDb.serviceUrl).toBe(runtimeProdUrl)

delete process.env.AIO_DB_ENVIRONMENT
const runtimeDefaultDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const runtimeDefaultDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(runtimeDefaultDb.serviceUrl).toBe(runtimeProdUrl)

// Simulate "aio app dev" by setting AIO_DEV
process.env.AIO_DEV = 'true'

process.env.AIO_DB_ENVIRONMENT = STAGE_ENV
const runtimeStageDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const runtimeStageDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(runtimeStageDevDb.serviceUrl).toBe(stageUrl)

process.env.AIO_DB_ENVIRONMENT = PROD_ENV
const runtimeProdDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const runtimeProdDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(runtimeProdDevDb.serviceUrl).toBe(externalProdUrl)

delete process.env.AIO_DB_ENVIRONMENT
const runtimeDefaultDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const runtimeDefaultDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(runtimeDefaultDevDb.serviceUrl).toBe(externalProdUrl)

// Test without __OW_ACTIVATION_ID (non-runtime context)
Expand All @@ -233,37 +233,37 @@ describe('DbBase tests', () => {

process.env.AIO_DB_ENVIRONMENT = STAGE_ENV
// Stage endpoint is the same for both contexts
const externalStageDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const externalStageDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(externalStageDb.serviceUrl).toBe(stageUrl)

process.env.AIO_DB_ENVIRONMENT = PROD_ENV
const externalProdDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const externalProdDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(externalProdDb.serviceUrl).toBe(externalProdUrl)

delete process.env.AIO_DB_ENVIRONMENT
const externalDefaultDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const externalDefaultDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(externalDefaultDb.serviceUrl).toBe(externalProdUrl)

// Make sure external behavior doesn't change if AIO_DEV is set
process.env.AIO_DEV = 'true'

process.env.AIO_DB_ENVIRONMENT = STAGE_ENV
const externalStageDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const externalStageDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(externalStageDevDb.serviceUrl).toBe(stageUrl)

process.env.AIO_DB_ENVIRONMENT = PROD_ENV
const externalProdDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const externalProdDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(externalProdDevDb.serviceUrl).toBe(externalProdUrl)

delete process.env.AIO_DB_ENVIRONMENT
const externalDefaultDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region })
const externalDefaultDevDb = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region })
expect(externalDefaultDevDb.serviceUrl).toBe(externalProdUrl)
})

test('db should be initialized in region from manifest when available', async () => {
getRegionFromAppConfig.mockReturnValue('emea')

const db = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH })
const db = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN })

expect(db.region).toBe('emea')
expect(getRegionFromAppConfig).toHaveBeenCalledWith(process.cwd())
Expand All @@ -272,7 +272,7 @@ describe('DbBase tests', () => {
test('db initialization should fall back to config.region when manifest is not available', async () => {
getRegionFromAppConfig.mockReturnValue(null)

const db = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region: 'apac' })
const db = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region: 'apac' })

expect(db.region).toBe('apac')
})
Expand All @@ -282,14 +282,14 @@ describe('DbBase tests', () => {
throw new Error('YAML parsing error')
})

await expect(DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH, region: 'amer' }))
await expect(DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN, region: 'amer' }))
.rejects.toThrow('Error reading region from app config: YAML parsing error')
})

test('db initialization should use default region when no manifest or config region available', async () => {
getRegionFromAppConfig.mockReturnValue(null)

const db = await DbBase.init({ namespace: TEST_NAMESPACE, apikey: TEST_AUTH })
const db = await DbBase.init({ namespace: TEST_NAMESPACE, token: TEST_ACCESS_TOKEN })

expect(db.region).toBe(ALLOWED_REGIONS[getCliEnv()].at(0))
})
Expand Down
Loading