From 36e979a35bef9e105d4c5f13fd034db4f1ad456b Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Wed, 28 Aug 2024 14:54:14 -0300 Subject: [PATCH 01/45] docs: update purge docs --- packages/purge/README.md | 26 ++++++++++-------------- packages/purge/src/index.ts | 37 ++++++++++++++++++---------------- packages/purge/src/services.ts | 34 +++++++++++++++++++++++++++++++ packages/purge/src/types.ts | 1 - 4 files changed, 65 insertions(+), 33 deletions(-) diff --git a/packages/purge/README.md b/packages/purge/README.md index 2d1dd04..74e041c 100644 --- a/packages/purge/README.md +++ b/packages/purge/README.md @@ -93,8 +93,7 @@ if (response) { **TypeScript:** ```typescript -import { purgeURL } from 'azion/purge'; -import { AzionPurge } from 'azion/purge/types'; +import { purgeURL, AzionPurge } from 'azion/purge'; const url: string[] = ['http://www.domain.com/path/image.jpg']; const response: AzionPurge | null = await purgeURL(url, { debug: true }); @@ -124,8 +123,7 @@ if (response) { **TypeScript:** ```typescript -import { purgeCacheKey } from 'azion/purge'; -import { AzionPurge } from 'azion/purge/types'; +import { purgeCacheKey, AzionPurge } from 'azion/purge'; const cacheKey: string[] = ['http://www.domain.com/path/image.jpg']; const response: AzionPurge | null = await purgeCacheKey(cacheKey, { debug: true }); @@ -155,8 +153,7 @@ if (response) { **TypeScript:** ```typescript -import { purgeWildCard } from 'azion/purge'; -import { AzionPurge } from 'azion/purge/types'; +import { purgeWildCard, AzionPurge } from 'azion/purge'; const wildcard: string[] = ['http://www.domain.com/path/image.jpg*']; const response: AzionPurge | null = await purgeWildCard(wildcard, { debug: true }); @@ -201,8 +198,7 @@ if (purgeWildCardResponse) { **TypeScript:** ```typescript -import { createClient } from 'azion/purge'; -import { AzionPurge, AzionPurgeClient } from 'azion/purge/types'; +import { createClient, AzionPurge, AzionPurgeClient } from 'azion/purge'; const client: AzionPurgeClient = createClient({ token: 'your-api-token', options: { debug: true } }); @@ -237,7 +233,7 @@ Purge a URL from the Azion Edge cache. **Parameters:** - `url: string[]` - URL(s) to purge. -- `debug?: boolean` - Enable debug mode for detailed logging. +- `options?: AzionClientOptions` - Client options including debug mode. **Returns:** @@ -250,7 +246,7 @@ Purge a Cache Key from the Azion Edge cache. **Parameters:** - `cacheKey: string[]` - Cache Key(s) to purge. -- `debug?: boolean` - Enable debug mode for detailed logging. +- `options?: AzionClientOptions` - Client options including debug mode. **Returns:** @@ -263,7 +259,7 @@ Purge using a wildcard expression from the Azion Edge cache. **Parameters:** - `wildcard: string[]` - Wildcard expression(s) to purge. -- `debug?: boolean` - Enable debug mode for detailed logging. +- `options?: AzionClientOptions` - Client options including debug mode. **Returns:** @@ -288,15 +284,15 @@ Creates a Purge client with methods to interact with Azion Edge Purge. Configuration options for the Purge client. - `token?: string` - Your Azion API token. -- `debug?: boolean` - Enable debug mode for detailed logging. +- `options?: AzionClientOptions` - Additional options for the client. ### `PurgeClient` An object with methods to interact with Purge. -- `purgeURL: (urls: string[]) => Promise` -- `purgeCacheKey: (cacheKeys: string[]) => Promise` -- `purgeWildCard: (wildcards: string[]) => Promise` +- `purgeURL: (urls: string[], options?: AzionClientOptions) => Promise` +- `purgeCacheKey: (cacheKeys: string[], options?: AzionClientOptions) => Promise` +- `purgeWildCard: (wildcards: string[], options?: AzionClientOptions) => Promise` ### `Purge` diff --git a/packages/purge/src/index.ts b/packages/purge/src/index.ts index b27395a..152c8e2 100644 --- a/packages/purge/src/index.ts +++ b/packages/purge/src/index.ts @@ -46,7 +46,7 @@ const purgeWildCardMethod = async ( * * @param {string[]} url - URLs to purge. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The purge response or null if the purge failed. + * @returns {Promise} The purge response or null if the purge failed. * * @example * const response = await purgeURL(['http://www.domain.com/path/image.jpg'], { debug: true }); @@ -64,7 +64,7 @@ const purgeURLWrapper = (url: string[], options?: AzionClientOptions): Promise} The purge response or null if the purge failed. + * @returns {Promise} The purge response or null if the purge failed. * * @example * const response = await purgeCacheKey(['http://www.domain.com/path/image.jpg'], { debug: true }); @@ -82,7 +82,7 @@ const purgeCacheKeyWrapper = (cacheKey: string[], options?: AzionClientOptions): * * @param {string[]} wildcard - Wildcard expressions to purge. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The purge response or null if the purge failed. + * @returns {Promise} The purge response or null if the purge failed. * * @example * const response = await purgeWildCard(['http://www.domain.com/path/image.jpg*'], { debug: true }); @@ -99,75 +99,78 @@ const purgeWildCardWrapper = (wildcard: string[], options?: AzionClientOptions): * Creates a Purge client with methods to interact with Azion Edge Purge. * * @param {Partial<{ token: string; options?: AzionClientOptions }>} [config] - Configuration options for the Purge client. - * @returns {PurgeClient} An object with methods to interact with Purge. + * @returns {AzionPurgeClient} An object with methods to interact with Purge. * * @example * const purgeClient = createClient({ token: 'your-api-token', options: { debug: true } }); * * // Purge a URL - * const purgeResult = await purgeClient.purgeURL(['http://www.domain.com/path/image.jpg']); + * const purgeResult = await purgeClient.purgeURL(['http://www.domain.com/path/image.jpg'], { debug: true }); * * // Purge a Cache Key - * const cacheKeyResult = await purgeClient.purgeCacheKey(['http://www.domain.com/path/image.jpg']); + * const cacheKeyResult = await purgeClient.purgeCacheKey(['http://www.domain.com/path/image.jpg'], { debug: true }); * * // Purge using a wildcard - * const wildcardResult = await purgeClient.purgeWildCard(['http://www.domain.com/path/image.jpg*']); + * const wildcardResult = await purgeClient.purgeWildCard(['http://www.domain.com/path/image.jpg*'], { debug: true }); */ const client: CreateAzionPurgeClient = ( config?: Partial<{ token: string; options?: AzionClientOptions }>, ): AzionPurgeClient => { const tokenValue = resolveToken(config?.token); - const options = config?.options ?? {}; const client: AzionPurgeClient = { /** * Purge a URL from the Azion Edge cache. * * @param {string[]} url - URLs to purge. - * @returns {Promise} The purge response or null if the purge failed. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} The purge response or null if the purge failed. * * @example - * const response = await purgeClient.purgeURL(['http://www.domain.com/path/image.jpg']); + * const response = await purgeClient.purgeURL(['http://www.domain.com/path/image.jpg'], { debug: true }); * if (response) { * console.log('Purge successful:', response); * } else { * console.error('Purge failed'); * } */ - purgeURL: (url: string[]): Promise => purgeURLMethod(tokenValue, url, options), + purgeURL: (url: string[], options?: AzionClientOptions): Promise => + purgeURLMethod(tokenValue, url, options), /** * Purge a Cache Key from the Azion Edge cache. * * @param {string[]} cacheKey - Cache Keys to purge. - * @returns {Promise} The purge response or null if the purge failed. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} The purge response or null if the purge failed. * * @example - * const response = await purgeClient.purgeCacheKey(['http://www.domain.com/path/image.jpg']); + * const response = await purgeClient.purgeCacheKey(['http://www.domain.com/path/image.jpg'], { debug: true }); * if (response) { * console.log('Purge successful:', response); * } else { * console.error('Purge failed'); * } */ - purgeCacheKey: (cacheKey: string[]): Promise => + purgeCacheKey: (cacheKey: string[], options?: AzionClientOptions): Promise => purgeCacheKeyMethod(tokenValue, cacheKey, options), /** * Purge using a wildcard expression from the Azion Edge cache. * * @param {string[]} wildcard - Wildcard expressions to purge. - * @returns {Promise} The purge response or null if the purge failed. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} The purge response or null if the purge failed. * * @example - * const response = await purgeClient.purgeWildCard(['http://www.domain.com/path/image.jpg*']); + * const response = await purgeClient.purgeWildCard(['http://www.domain.com/path/image.jpg*'], { debug: true }); * if (response) { * console.log('Purge successful:', response); * } else { * console.error('Purge failed'); * } */ - purgeWildCard: (wildcard: string[]): Promise => + purgeWildCard: (wildcard: string[], options?: AzionClientOptions): Promise => purgeWildCardMethod(tokenValue, wildcard, options), }; diff --git a/packages/purge/src/services.ts b/packages/purge/src/services.ts index c1bd8ad..5fd23b3 100644 --- a/packages/purge/src/services.ts +++ b/packages/purge/src/services.ts @@ -2,18 +2,51 @@ import { ApiPurgeResponse } from './types'; const BASE_URL = 'https://api.azion.com/v4/edge/purge'; +/** + * Purge URLs from the Azion Edge cache. + * + * @param {string} token - Authentication token for Azion API. + * @param {string[]} urls - URLs to purge. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The purge response or null if the purge failed. + */ const postPurgeURL = async (token: string, urls: string[], debug?: boolean): Promise => { return postPurge(`${BASE_URL}/url`, token, urls, debug); }; +/** + * Purge cache keys from the Azion Edge cache. + * + * @param {string} token - Authentication token for Azion API. + * @param {string[]} urls - Cache keys to purge. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The purge response or null if the purge failed. + */ const postPurgeCacheKey = async (token: string, urls: string[], debug?: boolean): Promise => { return postPurge(`${BASE_URL}/cachekey`, token, urls, debug); }; +/** + * Purge using wildcard expressions from the Azion Edge cache. + * + * @param {string} token - Authentication token for Azion API. + * @param {string[]} urls - Wildcard expressions to purge. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The purge response or null if the purge failed. + */ const postPurgeWildcard = async (token: string, urls: string[], debug?: boolean): Promise => { return postPurge(`${BASE_URL}/wildcard`, token, urls, debug); }; +/** + * Helper function to send a purge request to the Azion Edge cache. + * + * @param {string} url - The API endpoint for the purge request. + * @param {string} token - Authentication token for Azion API. + * @param {string[]} urls - Items to purge. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The purge response or null if the purge failed. + */ const postPurge = async ( url: string, token: string, @@ -28,6 +61,7 @@ const postPurge = async ( 'Content-Type': 'application/json', Accept: 'application/json; version=3', }, + credentials: 'include', body: JSON.stringify({ items: urls, layer: 'edge_cache' }), }); const data = await response.json(); diff --git a/packages/purge/src/types.ts b/packages/purge/src/types.ts index 492804d..289be01 100644 --- a/packages/purge/src/types.ts +++ b/packages/purge/src/types.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ export interface ApiPurgeResponse { state: 'executed' | 'pending'; data: { From 3530fd3f180735c2438680746ae504729f7a1306 Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Thu, 29 Aug 2024 11:28:31 -0300 Subject: [PATCH 02/45] docs: update storage docs --- packages/client/src/index.ts | 8 +- packages/storage/README.md | 321 ++-------- packages/storage/src/index.test.ts | 70 ++- packages/storage/src/index.ts | 567 +++++++++++------- packages/storage/src/services/api/index.ts | 102 +++- .../storage/src/services/runtime/index.ts | 76 ++- packages/storage/src/types.ts | 37 +- 7 files changed, 613 insertions(+), 568 deletions(-) diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 7cafc5c..7ef649e 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -31,7 +31,7 @@ import { AzionClient, AzionClientConfig } from './types'; * * @example * // Use the Storage client - * const buckets = await client.storage.getBuckets(); + * const buckets = await client.storage.getBuckets({ params: { page: 1, page_size: 10 } }); * * @example * // Use the Purge client @@ -48,13 +48,13 @@ function createClient({ token, options }: AzionClientConfig = {}): AzionClient { * * @example * // Create a new bucket - * const newBucket = await client.storage.createBucket('my-new-bucket', 'public'); + * const newBucket = await client.storage.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); * * // Get all buckets - * const allBuckets = await client.storage.getBuckets(); + * const allBuckets = await client.storage.getBuckets({ params: { page: 1, page_size: 10 } }); * * // Delete a bucket - * const deletedBucket = await client.storage.deleteBucket('my-bucket'); + * const deletedBucket = await client.storage.deleteBucket({ name: 'my-bucket' }); */ storage: storageClient, /** diff --git a/packages/storage/README.md b/packages/storage/README.md index b0c8e63..05bbf56 100644 --- a/packages/storage/README.md +++ b/packages/storage/README.md @@ -98,7 +98,7 @@ You can create a client instance with specific configurations. ```javascript import { createBucket } from 'azion/storage'; -const bucket = await createBucket('my-new-bucket', 'public', true); +const bucket = await createBucket({ name: 'my-new-bucket', edge_access: 'public' }); if (bucket) { console.log(`Bucket created with name: ${bucket.name}`); } else { @@ -109,10 +109,8 @@ if (bucket) { **TypeScript:** ```typescript -import { createBucket } from 'azion/storage'; -import { AzionBucket } from 'azion/storage/types'; - -const bucket: AzionBucket | null = await createBucket('my-new-bucket', 'public', true); +import { createBucket, AzionBucket } from 'azion/storage'; +const bucket: AzionBucket | null = await createBucket({ name: 'my-new-bucket', edge_access: 'public' }); if (bucket) { console.log(`Bucket created with name: ${bucket.name}`); } else { @@ -127,7 +125,7 @@ if (bucket) { ```javascript import { deleteBucket } from 'azion/storage'; -const result = await deleteBucket('my-bucket', true); +const result = await deleteBucket({ name: 'my-bucket' }); if (result) { console.log(`Bucket ${result.name} deleted successfully`); } else { @@ -138,10 +136,9 @@ if (result) { **TypeScript:** ```typescript -import { deleteBucket } from 'azion/storage'; -import { AzionDeletedBucket } from 'azion/storage/types'; +import { deleteBucket, AzionDeletedBucket } from 'azion/storage'; -const result: AzionDeletedBucket | null = await deleteBucket('my-bucket', true); +const result: AzionDeletedBucket | null = await deleteBucket({ name: 'my-bucket' }); if (result) { console.log(`Bucket ${result.name} deleted successfully`); } else { @@ -156,7 +153,7 @@ if (result) { ```javascript import { getBuckets } from 'azion/storage'; -const buckets = await getBuckets({ page: 1, page_size: 10 }, true); +const buckets = await getBuckets({ params: { page: 1, page_size: 10 } }); if (buckets) { console.log(`Retrieved ${buckets.length} buckets`); } else { @@ -167,10 +164,9 @@ if (buckets) { **TypeScript:** ```typescript -import { getBuckets } from 'azion/storage'; -import { AzionBucket } from 'azion/storage/types'; +import { getBuckets, AzionBucket } from 'azion/storage'; -const buckets: AzionBucket[] | null = await getBuckets({ page: 1, page_size: 10 }, true); +const buckets: AzionBucket[] | null = await getBuckets({ params: { page: 1, page_size: 10 } }); if (buckets) { console.log(`Retrieved ${buckets.length} buckets`); } else { @@ -185,7 +181,7 @@ if (buckets) { ```javascript import { getBucket } from 'azion/storage'; -const bucket = await getBucket('my-bucket', true); +const bucket = await getBucket({ name: 'my-bucket' }); if (bucket) { console.log(`Retrieved bucket: ${bucket.name}`); } else { @@ -196,10 +192,9 @@ if (bucket) { **TypeScript:** ```typescript -import { getBucket } from 'azion/storage'; -import { AzionBucket } from 'azion/storage/types'; +import { getBucket, AzionBucket } from 'azion/storage'; -const bucket: AzionBucket | null = await getBucket('my-bucket', true); +const bucket: AzionBucket | null = await getBucket({ name: 'my-bucket' }); if (bucket) { console.log(`Retrieved bucket: ${bucket.name}`); } else { @@ -214,7 +209,7 @@ if (bucket) { ```javascript import { updateBucket } from 'azion/storage'; -const updatedBucket = await updateBucket('my-bucket', 'private', true); +const updatedBucket = await updateBucket({ name: 'my-bucket', edge_access: 'private' }); if (updatedBucket) { console.log(`Bucket updated: ${updatedBucket.name}`); } else { @@ -225,10 +220,9 @@ if (updatedBucket) { **TypeScript:** ```typescript -import { updateBucket } from 'azion/storage'; -import { AzionBucket } from 'azion/storage/types'; +import { updateBucket, AzionBucket } from 'azion/storage'; -const updatedBucket: AzionBucket | null = await updateBucket('my-bucket', 'private', true); +const updatedBucket: AzionBucket | null = await updateBucket({ name: 'my-bucket', edge_access: 'private' }); if (updatedBucket) { console.log(`Bucket updated: ${updatedBucket.name}`); } else { @@ -243,7 +237,7 @@ if (updatedBucket) { ```javascript import { createObject } from 'azion/storage'; -const newObject = await createObject('my-bucket', 'new-file.txt', 'File content', true); +const newObject = await createObject({ bucketName: 'my-bucket', key: 'new-file.txt', file: 'File content' }); if (newObject) { console.log(`Object created with key: ${newObject.key}`); console.log(`Object content: ${newObject.content}`); @@ -255,10 +249,13 @@ if (newObject) { **TypeScript:** ```typescript -import { createObject } from 'azion/storage'; -import { AzionBucketObject } from 'azion/storage/types'; +import { createObject, AzionBucketObject } from 'azion/storage'; -const newObject: AzionBucketObject | null = await createObject('my-bucket', 'new-file.txt', 'File content', true); +const newObject: AzionBucketObject | null = await createObject({ + bucketName: 'my-bucket', + key: 'new-file.txt', + file: 'File content', +}); if (newObject) { console.log(`Object created with key: ${newObject.key}`); console.log(`Object content: ${newObject.content}`); @@ -274,7 +271,7 @@ if (newObject) { ```javascript import { getObjectByKey } from 'azion/storage'; -const object = await getObjectByKey('my-bucket', 'file.txt', true); +const object = await getObjectByKey({ bucketName: 'my-bucket', key: 'file.txt' }); if (object) { console.log(`Retrieved object: ${object.key}`); } else { @@ -285,10 +282,9 @@ if (object) { **TypeScript:** ```typescript -import { getObjectByKey } from 'azion/storage'; -import { AzionBucketObject } from 'azion/storage/types'; +import { getObjectByKey, AzionBucketObject } from 'azion/storage'; -const object: AzionBucketObject | null = await getObjectByKey('my-bucket', 'file.txt', true); +const object: AzionBucketObject | null = await getObjectByKey({ bucketName: 'my-bucket', key: 'file.txt' }); if (object) { console.log(`Retrieved object: ${object.key}`); } else { @@ -303,7 +299,7 @@ if (object) { ```javascript import { getObjects } from 'azion/storage'; -const objects = await getObjects('my-bucket', true); +const objects = await getObjects({ bucketName: 'my-bucket' }); if (objects) { console.log(`Retrieved ${objects.length} objects from the bucket`); } else { @@ -314,10 +310,9 @@ if (objects) { **TypeScript:** ```typescript -import { getObjects } from 'azion/storage'; -import { AzionBucketObject } from 'azion/storage/types'; +import { getObjects, AzionBucketObject } from 'azion/storage'; -const objects: AzionBucketObject[] | null = await getObjects('my-bucket', true); +const objects: AzionBucketObject[] | null = await getObjects({ bucketName: 'my-bucket' }); if (objects) { console.log(`Retrieved ${objects.length} objects from the bucket`); } else { @@ -332,7 +327,7 @@ if (objects) { ```javascript import { updateObject } from 'azion/storage'; -const updatedObject = await updateObject('my-bucket', 'file.txt', 'Updated content', true); +const updatedObject = await updateObject({ bucketName: 'my-bucket', key: 'file.txt', file: 'Updated content' }); if (updatedObject) { console.log(`Object updated: ${updatedObject.key}`); console.log(`New content: ${updatedObject.content}`); @@ -344,10 +339,13 @@ if (updatedObject) { **TypeScript:** ```typescript -import { updateObject } from 'azion/storage'; -import { AzionBucketObject } from 'azion/storage/types'; +import { updateObject, AzionBucketObject } from 'azion/storage'; -const updatedObject: AzionBucketObject | null = await updateObject('my-bucket', 'file.txt', 'Updated content', true); +const updatedObject: AzionBucketObject | null = await updateObject({ + bucketName: 'my-bucket', + key: 'file.txt', + file: 'Updated content', +}); if (updatedObject) { console.log(`Object updated: ${updatedObject.key}`); console.log(`New content: ${updatedObject.content}`); @@ -363,7 +361,7 @@ if (updatedObject) { ```javascript import { deleteObject } from 'azion/storage'; -const result = await deleteObject('my-bucket', 'file.txt', true); +const result = await deleteObject({ bucketName: 'my-bucket', key: 'file.txt' }); if (result) { console.log(`Object ${result.key} deleted successfully`); } else { @@ -374,10 +372,9 @@ if (result) { **TypeScript:** ```typescript -import { deleteObject } from 'azion/storage'; -import { AzionDeletedBucketObject } from 'azion/storage/types'; +import { deleteObject, AzionDeletedBucketObject } from 'azion/storage'; -const result: AzionDeletedBucketObject | null = await deleteObject('my-bucket', 'file.txt', true); +const result: AzionDeletedBucketObject | null = await deleteObject({ bucketName: 'my-bucket', key: 'file.txt' }); if (result) { console.log(`Object ${result.key} deleted successfully`); } else { @@ -394,7 +391,7 @@ import { createClient } from 'azion/storage'; const client = createClient({ token: 'your-api-token', debug: true }); -const newBucket = await client.createBucket('my-new-bucket', 'public'); +const newBucket = await client.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); if (newBucket) { console.log(`Bucket created with name: ${newBucket.name}`); } @@ -404,12 +401,12 @@ if (allBuckets) { console.log(`Retrieved ${allBuckets.length} buckets`); } -const newObject = await client.createObject('my-new-bucket', 'new-file.txt', 'File content'); +const newObject = await client.createObject({ bucketName: 'my-new-bucket', key: 'new-file.txt', file: 'File content' }); if (newObject) { console.log(`Object created with key: ${newObject.key}`); } -const queryResult = await newObject.updateObject('new-file.txt', 'Updated content'); +const queryResult = await newObject.updateObject({ key: 'new-file.txt', file: 'Updated content' }); if (queryResult) { console.log(`Object updated with key: ${queryResult.key}`); } @@ -418,12 +415,11 @@ if (queryResult) { **TypeScript:** ```typescript -import { createClient } from 'azion/storage'; -import { StorageClient, AzionBucket, AzionBucketObject } from 'azion/storage/types'; +import { createClient, StorageClient, AzionBucket, AzionBucketObject } from 'azion/storage'; const client: StorageClient = createClient({ token: 'your-api-token', debug: true }); -const newBucket: AzionBucket | null = await client.createBucket('my-new-bucket', 'public'); +const newBucket: AzionBucket | null = await client.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); if (newBucket) { console.log(`Bucket created with name: ${newBucket.name}`); } @@ -433,228 +429,21 @@ if (allBuckets) { console.log(`Retrieved ${allBuckets.length} buckets`); } -const newObject: AzionBucketObject | null = await client.createObject('my-new-bucket', 'new-file.txt', 'File content'); +const newObject: AzionBucketObject | null = await client.createObject({ + bucketName: 'my-new-bucket', + key: 'new-file.txt', + file: 'File content', +}); if (newObject) { console.log(`Object created with key: ${newObject.key}`); } -const updatedObject: AzionBucketObject | null = await newObject.updateObject('new-file.txt', 'Updated content'); -if (updatedObject) { - console.log(`Object updated with key: ${updatedObject.key}`); +const queryResult: AzionBucketObject | null = await client.updateObject({ + bucketName: 'my-new-bucket', + key: 'new-file.txt', + file: 'Updated content', +}); +if (queryResult) { + console.log(`Object updated with key: ${queryResult.key}`); } ``` - -## API Reference - -### `createBucket` - -Creates a new bucket. - -**Parameters:** - -- `name: string` - Name of the new bucket. -- `edge_access: string` - Edge access configuration for the bucket. -- `debug?: boolean` - Enable debug mode for detailed logging. - -**Returns:** - -- `Promise` - The created bucket object or null if creation failed. - -### `deleteBucket` - -Deletes a bucket by its name. - -**Parameters:** - -- `name: string` - Name of the bucket to delete. -- `debug?: boolean` - Enable debug mode for detailed logging. - -**Returns:** - -- `Promise` - Object confirming deletion or null if deletion failed. - -### `getBuckets` - -Retrieves a list of buckets with optional filtering and pagination. - -**Parameters:** - -- `options?: AzionBucketCollectionOptions` - Optional parameters for filtering and pagination. - - `page?: number` - Page number for pagination. - - `page_size?: number` - Number of items per page. -- `debug?: boolean` - Enable debug mode for detailed logging. - -**Returns:** - -- `Promise` - Array of bucket objects or null if retrieval failed. - -### `getBucket` - -Retrieves a bucket by its name. - -**Parameters:** - -- `name: string` - Name of the bucket to retrieve. -- `debug?: boolean` - Enable debug mode for detailed logging. - -**Returns:** - -- `Promise` - The retrieved bucket object or null if not found. - -### `updateBucket` - -Updates an existing bucket. - -**Parameters:** - -- `name: string` - Name of the bucket to update. -- `edge_access: string` - New edge access configuration for the bucket. -- `debug?: boolean` - Enable debug mode for detailed logging. - -**Returns:** - -- `Promise` - The updated bucket object or null if update failed. - -### `createObject` - -Creates a new object in a specific bucket. - -**Parameters:** - -- `bucketName: string` - Name of the bucket to create the object in. -- `objectKey: string` - Key (name) of the object to create. -- `file: string` - Content of the file to upload. -- `debug?: boolean` - Enable debug mode for detailed logging. - -**Returns:** - -- `Promise` - The created object or null if creation failed. - -### `getObjectByKey` - -Retrieves an object from a specific bucket by its key. - -**Parameters:** - -- `bucketName: string` - Name of the bucket containing the object. -- `objectKey: string` - Key of the object to retrieve. -- `debug?: boolean` - Enable debug mode for detailed logging. - -**Returns:** - -- `Promise` - The retrieved object or null if not found. - -### `getObjects` - -Retrieves a list of objects in a specific bucket. - -**Parameters:** - -- `bucketName: string` - Name of the bucket to retrieve objects from. -- `debug?: boolean` - Enable debug mode for detailed logging. - -**Returns:** - -- `Promise` - Array of bucket objects or null if retrieval failed. - -### `updateObject` - -Updates an existing object in a specific bucket. - -**Parameters:** - -- `bucketName: string` - Name of the bucket containing the object. -- `objectKey: string` - Key of the object to update. -- `file: string` - New content of the file. -- `debug?: boolean` - Enable debug mode for detailed logging. - -**Returns:** - -- `Promise` - The updated object or null if update failed. - -### `deleteObject` - -Deletes an object from a specific bucket. - -**Parameters:** - -- `bucketName: string` - Name of the bucket containing the object. -- `objectKey: string` - Key of the object to delete. -- `debug?: boolean` - Enable debug mode for detailed logging. - -**Returns:** - -- `Promise` - Confirmation of deletion or null if deletion failed. - -### `createClient` - -Creates a Storage client with methods to interact with Azion Edge Storage. - -**Parameters:** - -- `config?: Partial<{ token: string; debug: boolean }>` - Configuration options for the Storage client. - -**Returns:** - -- `StorageClient` - An object with methods to interact with Storage. - -## Types - -### `ClientConfig` - -Configuration options for the Storage client. - -- `token?: string` - Your Azion API token. -- `debug?: boolean` - Enable debug mode for detailed logging. - -### `StorageClient` - -An object with methods to interact with Storage. - -- `getBuckets: (options?: BucketCollectionOptions) => Promise` -- `createBucket: (name: string, edge_access: string) => Promise` -- `updateBucket: (name: string, edge_access: string) => Promise` -- `deleteBucket: (name: string) => Promise` -- `getBucket: (name: string) => Promise` - -### `AzionBucket` - -The bucket object. - -- `name: string` -- `edge_access?: string` -- `state?: 'executed' | 'pending'` -- `getObjects?: () => Promise` -- `getObjectByKey?: (objectKey: string) => Promise` -- `createObject?: (objectKey: string, file: string) => Promise` -- `updateObject?: (objectKey: string, file: string) => Promise` -- `deleteObject?: (objectKey: string) => Promise` - -### `AzionBucketObject` - -The bucket object. - -- `key: string` -- `state?: 'executed' | 'pending'` -- `size?: number` -- `last_modified?: string` -- `content_type?: string` -- `content?: string` - -### `AzionDeletedBucket` - -The response object from a delete bucket request. - -- `name: string` -- `state: 'executed' | 'pending'` - -### `AzionDeletedBucketObject` - -The response object from a delete object request. - -- `key: string` -- `state: 'executed' | 'pending'` - -## Contributing - -Feel free to submit issues or pull requests to improve the functionality or documentation. diff --git a/packages/storage/src/index.test.ts b/packages/storage/src/index.test.ts index e81fc4c..94e62f9 100644 --- a/packages/storage/src/index.test.ts +++ b/packages/storage/src/index.test.ts @@ -54,7 +54,7 @@ describe('Storage Module', () => { const mockResponse = { data: { name: 'test-bucket', edge_access: 'public' } }; (services.postBucket as jest.Mock).mockResolvedValue(mockResponse); - const result = await createBucket('test-bucket', 'public', { debug }); + const result = await createBucket({ name: 'test-bucket', edge_access: 'public', options: { debug } }); expect(result).toEqual(expect.objectContaining({ name: 'test-bucket', edge_access: 'public' })); expect(services.postBucket).toHaveBeenCalledWith(mockToken, 'test-bucket', 'public', debug); }); @@ -62,7 +62,7 @@ describe('Storage Module', () => { it('should return null on failure', async () => { (services.postBucket as jest.Mock).mockResolvedValue(null); - const result = await createBucket('test-bucket', 'public', { debug }); + const result = await createBucket({ name: 'test-bucket', edge_access: 'public', options: { debug } }); expect(result).toBeNull(); }); }); @@ -72,7 +72,7 @@ describe('Storage Module', () => { const mockResponse = { data: { name: 'test-bucket' }, state: 'success' }; (services.deleteBucket as jest.Mock).mockResolvedValue(mockResponse); - const result = await deleteBucket('test-bucket', { debug }); + const result = await deleteBucket({ name: 'test-bucket', options: { debug } }); expect(result).toEqual({ name: 'test-bucket', state: 'success' }); expect(services.deleteBucket).toHaveBeenCalledWith(mockToken, 'test-bucket', debug); }); @@ -80,7 +80,7 @@ describe('Storage Module', () => { it('should return null on failure', async () => { (services.deleteBucket as jest.Mock).mockResolvedValue(null); - const result = await deleteBucket('test-bucket', { debug }); + const result = await deleteBucket({ name: 'test-bucket', options: { debug } }); expect(result).toBeNull(); }); }); @@ -90,7 +90,7 @@ describe('Storage Module', () => { const mockResponse = { results: [{ name: 'bucket1' }, { name: 'bucket2' }] }; (services.getBuckets as jest.Mock).mockResolvedValue(mockResponse); - const result = await getBuckets({ page: 1, page_size: 10 }, { debug }); + const result = await getBuckets({ params: { page: 1, page_size: 10 }, options: { debug } }); expect(result).toHaveLength(2); expect(result![0]).toHaveProperty('name', 'bucket1'); expect(services.getBuckets).toHaveBeenCalledWith(mockToken, { page: 1, page_size: 10 }, debug); @@ -99,7 +99,7 @@ describe('Storage Module', () => { it('should return null on failure', async () => { (services.getBuckets as jest.Mock).mockResolvedValue(null); - const result = await getBuckets({ page: 1, page_size: 10 }, { debug }); + const result = await getBuckets({ params: { page: 1, page_size: 10 }, options: { debug } }); expect(result).toBeNull(); }); }); @@ -109,7 +109,7 @@ describe('Storage Module', () => { const mockResponse = { results: [{ name: 'test-bucket', edge_access: 'public' }] }; (services.getBuckets as jest.Mock).mockResolvedValue(mockResponse); - const result = await getBucket('test-bucket', { debug }); + const result = await getBucket({ name: 'test-bucket', options: { debug } }); expect(result).toEqual(expect.objectContaining({ name: 'test-bucket', edge_access: 'public' })); expect(services.getBuckets).toHaveBeenCalledWith(mockToken, { page_size: 1000000 }, debug); }); @@ -118,7 +118,7 @@ describe('Storage Module', () => { const mockResponse = { results: [] }; (services.getBuckets as jest.Mock).mockResolvedValue(mockResponse); - const result = await getBucket('non-existent-bucket', { debug }); + const result = await getBucket({ name: 'non-existent-bucket', options: { debug } }); expect(result).toBeNull(); }); }); @@ -128,7 +128,7 @@ describe('Storage Module', () => { const mockResponse = { data: { name: 'test-bucket', edge_access: 'private' } }; (services.patchBucket as jest.Mock).mockResolvedValue(mockResponse); - const result = await updateBucket('test-bucket', 'private', { debug }); + const result = await updateBucket({ name: 'test-bucket', edge_access: 'private', options: { debug } }); expect(result).toEqual(expect.objectContaining({ name: 'test-bucket', edge_access: 'private' })); expect(services.patchBucket).toHaveBeenCalledWith(mockToken, 'test-bucket', 'private', debug); }); @@ -136,7 +136,7 @@ describe('Storage Module', () => { it('should return null on failure', async () => { (services.patchBucket as jest.Mock).mockResolvedValue(null); - const result = await updateBucket('test-bucket', 'private', { debug }); + const result = await updateBucket({ name: 'test-bucket', edge_access: 'private', options: { debug } }); expect(result).toBeNull(); }); }); @@ -146,7 +146,12 @@ describe('Storage Module', () => { const mockResponse = { data: { object_key: 'test-object' }, state: 'success' }; (services.postObject as jest.Mock).mockResolvedValue(mockResponse); - const result = await createObject('test-bucket', 'test-object', 'file-content', { debug }); + const result = await createObject({ + bucket: 'test-bucket', + key: 'test-object', + content: 'file-content', + options: { debug }, + }); expect(result).toEqual({ key: 'test-object', content: 'file-content', state: 'success' }); expect(services.postObject).toHaveBeenCalledWith(mockToken, 'test-bucket', 'test-object', 'file-content', debug); }); @@ -154,7 +159,12 @@ describe('Storage Module', () => { it('should return null on failure', async () => { (services.postObject as jest.Mock).mockResolvedValue(null); - const result = await createObject('test-bucket', 'test-object', 'file-content', { debug }); + const result = await createObject({ + bucket: 'test-bucket', + key: 'test-object', + content: 'file-content', + options: { debug }, + }); expect(result).toBeNull(); }); }); @@ -164,7 +174,7 @@ describe('Storage Module', () => { const mockResponse = { state: 'success' }; (services.deleteObject as jest.Mock).mockResolvedValue(mockResponse); - const result = await deleteObject('test-bucket', 'test-object', { debug }); + const result = await deleteObject({ bucket: 'test-bucket', key: 'test-object', options: { debug } }); expect(result).toEqual({ key: 'test-object', state: 'success' }); expect(services.deleteObject).toHaveBeenCalledWith(mockToken, 'test-bucket', 'test-object', debug); }); @@ -172,7 +182,7 @@ describe('Storage Module', () => { it('should return null on failure', async () => { (services.deleteObject as jest.Mock).mockResolvedValue(null); - const result = await deleteObject('test-bucket', 'test-object', { debug }); + const result = await deleteObject({ bucket: 'test-bucket', key: 'test-object', options: { debug } }); expect(result).toBeNull(); }); }); @@ -182,7 +192,7 @@ describe('Storage Module', () => { const mockResponse = 'file-content'; (services.getObjectByKey as jest.Mock).mockResolvedValue(mockResponse); - const result = await getObjectByKey('test-bucket', 'test-object', { debug }); + const result = await getObjectByKey({ bucket: 'test-bucket', key: 'test-object', options: { debug } }); expect(result).toEqual({ key: 'test-object', content: 'file-content' }); expect(services.getObjectByKey).toHaveBeenCalledWith(mockToken, 'test-bucket', 'test-object', debug); }); @@ -190,7 +200,7 @@ describe('Storage Module', () => { it('should return null on failure', async () => { (services.getObjectByKey as jest.Mock).mockResolvedValue(null); - const result = await getObjectByKey('test-bucket', 'test-object', { debug }); + const result = await getObjectByKey({ bucket: 'test-bucket', key: 'test-object', options: { debug } }); expect(result).toBeNull(); }); }); @@ -205,7 +215,7 @@ describe('Storage Module', () => { }; (services.getObjects as jest.Mock).mockResolvedValue(mockResponse); - const result = await getObjects('test-bucket', { max_object_count: 50 }, { debug }); + const result = await getObjects({ bucket: 'test-bucket', params: { max_object_count: 50 }, options: { debug } }); expect(result).toHaveLength(2); expect(result![0]).toEqual(expect.objectContaining({ key: 'object1' })); expect(services.getObjects).toHaveBeenCalledWith(mockToken, 'test-bucket', { max_object_count: 50 }, debug); @@ -214,7 +224,7 @@ describe('Storage Module', () => { it('should return null on failure', async () => { (services.getObjects as jest.Mock).mockResolvedValue(null); - const result = await getObjects('test-bucket', { max_object_count: 50 }, { debug }); + const result = await getObjects({ bucket: 'test-bucket', params: { max_object_count: 50 }, options: { debug } }); expect(result).toBeNull(); }); }); @@ -224,7 +234,12 @@ describe('Storage Module', () => { const mockResponse = { data: { object_key: 'test-object' }, state: 'success' }; (services.putObject as jest.Mock).mockResolvedValue(mockResponse); - const result = await updateObject('test-bucket', 'test-object', 'updated-content', { debug }); + const result = await updateObject({ + bucket: 'test-bucket', + key: 'test-object', + content: 'updated-content', + options: { debug }, + }); expect(result).toEqual({ key: 'test-object', content: 'updated-content', state: 'success' }); expect(services.putObject).toHaveBeenCalledWith( mockToken, @@ -238,7 +253,12 @@ describe('Storage Module', () => { it('should return null on failure', async () => { (services.putObject as jest.Mock).mockResolvedValue(null); - const result = await updateObject('test-bucket', 'test-object', 'updated-content', { debug }); + const result = await updateObject({ + bucket: 'test-bucket', + key: 'test-object', + content: 'updated-content', + options: { debug }, + }); expect(result).toBeNull(); }); }); @@ -255,7 +275,7 @@ describe('Storage Module', () => { const mockResponse = { results: [{ name: 'bucket1' }, { name: 'bucket2' }] }; (services.getBuckets as jest.Mock).mockResolvedValue(mockResponse); - await client.getBuckets({ page: 1, page_size: 10 }); + await client.getBuckets({ params: { page: 1, page_size: 10 } }); expect(services.getBuckets).toHaveBeenCalledWith('custom-token', { page: 1, page_size: 10 }, debug); }); @@ -263,7 +283,7 @@ describe('Storage Module', () => { const mockResponse = { data: { name: 'test-bucket', edge_access: 'public' } }; (services.postBucket as jest.Mock).mockResolvedValue(mockResponse); - await client.createBucket('test-bucket', 'public'); + await client.createBucket({ name: 'test-bucket', edge_access: 'public' }); expect(services.postBucket).toHaveBeenCalledWith('custom-token', 'test-bucket', 'public', debug); }); @@ -271,7 +291,7 @@ describe('Storage Module', () => { const mockResponse = { data: { name: 'test-bucket', edge_access: 'private' } }; (services.patchBucket as jest.Mock).mockResolvedValue(mockResponse); - await client.updateBucket('test-bucket', 'private'); + await client.updateBucket({ name: 'test-bucket', edge_access: 'private' }); expect(services.patchBucket).toHaveBeenCalledWith('custom-token', 'test-bucket', 'private', debug); }); @@ -279,7 +299,7 @@ describe('Storage Module', () => { const mockResponse = { data: { name: 'test-bucket' }, state: 'success' }; (services.deleteBucket as jest.Mock).mockResolvedValue(mockResponse); - await client.deleteBucket('test-bucket'); + await client.deleteBucket({ name: 'test-bucket' }); expect(services.deleteBucket).toHaveBeenCalledWith('custom-token', 'test-bucket', debug); }); @@ -287,7 +307,7 @@ describe('Storage Module', () => { const mockResponse = { results: [{ name: 'test-bucket', edge_access: 'public' }] }; (services.getBuckets as jest.Mock).mockResolvedValue(mockResponse); - await client.getBucket('test-bucket'); + await client.getBucket({ name: 'test-bucket' }); expect(services.getBuckets).toHaveBeenCalledWith('custom-token', { page_size: 1000000 }, debug); }); }); diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts index 054d921..5f18aab 100644 --- a/packages/storage/src/index.ts +++ b/packages/storage/src/index.ts @@ -48,6 +48,15 @@ const createInternalOrExternalMethod = any>(inter }) as T; }; +/** + * Creates a new bucket. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} name - Name of the bucket to create. + * @param {string} edge_access - Edge access configuration for the bucket. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} The created bucket object or null if creation failed. + */ export const createBucketMethod = async ( token: string, name: string, @@ -58,21 +67,29 @@ export const createBucketMethod = async ( if (apiResponse) { return { ...apiResponse.data, - getObjects: (params: AzionObjectCollectionParams): Promise => - getObjectsMethod(token, name, params, options), - getObjectByKey: (objectKey: string): Promise => - getObjectByKeyMethod(token, name, objectKey, options), - createObject: (objectKey: string, file: string): Promise => - createObjectMethod(token, name, objectKey, file, options), - updateObject: (objectKey: string, file: string): Promise => - updateObjectMethod(token, name, objectKey, file, options), - deleteObject: (objectKey: string): Promise => - deleteObjectMethod(token, name, objectKey, options), + getObjects: ({ params }: { params: AzionObjectCollectionParams }): Promise => + getObjectsMethod(token, name, params), + getObjectByKey: ({ key }: { key: string }): Promise => + getObjectByKeyMethod(token, name, key), + createObject: ({ key, content }: { key: string; content: string }): Promise => + createObjectMethod(token, name, key, content), + updateObject: ({ key, content }: { key: string; content: string }): Promise => + updateObjectMethod(token, name, key, content), + deleteObject: ({ key }: { key: string }): Promise => + deleteObjectMethod(token, name, key), }; } return null; }; +/** + * Deletes a bucket by its name. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} name - Name of the bucket to delete. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} Confirmation of deletion or null if deletion failed. + */ export const deleteBucketMethod = async ( token: string, name: string, @@ -85,6 +102,14 @@ export const deleteBucketMethod = async ( return null; }; +/** + * Retrieves a list of buckets with optional filtering and pagination. + * + * @param {string} token - Authentication token for Azion API. + * @param {AzionBucketCollectionParams} [params] - Optional parameters for filtering and pagination. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} Array of bucket objects or null if retrieval failed. + */ export const getBucketsMethod = async ( token: string, params?: AzionBucketCollectionParams, @@ -94,21 +119,29 @@ export const getBucketsMethod = async ( if (apiResponse) { return apiResponse.results?.map((bucket) => ({ ...bucket, - getObjects: (params: AzionObjectCollectionParams): Promise => - getObjectsMethod(token, bucket.name, params, options), - getObjectByKey: (objectKey: string): Promise => - getObjectByKeyMethod(token, bucket.name, objectKey, options), - createObject: (objectKey: string, file: string): Promise => - createObjectMethod(token, bucket.name, objectKey, file, options), - updateObject: (objectKey: string, file: string): Promise => - updateObjectMethod(token, bucket.name, objectKey, file, options), - deleteObject: (objectKey: string): Promise => - deleteObjectMethod(token, bucket.name, objectKey, options), + getObjects: ({ params }: { params: AzionObjectCollectionParams }): Promise => + getObjectsMethod(token, bucket.name, params), + getObjectByKey: ({ key }: { key: string }): Promise => + getObjectByKeyMethod(token, bucket.name, key), + createObject: ({ key, content }: { key: string; content: string }): Promise => + createObjectMethod(token, bucket.name, key, content), + updateObject: ({ key, content }: { key: string; content: string }): Promise => + updateObjectMethod(token, bucket.name, key, content), + deleteObject: ({ key }: { key: string }): Promise => + deleteObjectMethod(token, bucket.name, key), })); } return null; }; +/** + * Get a bucket by its name. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} name - Name of the bucket to get. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} - Bucket object or null if retrieval failed. + */ const getBucketMethod = createInternalOrExternalMethod( async (token: string, name: string, options?: AzionClientOptions): Promise => { // NOTE: This is a temporary solution because the API does not provide an endpoint @@ -118,7 +151,7 @@ const getBucketMethod = createInternalOrExternalMethod( if (!bucket) return null; const internalClient = new InternalStorageClient(token, options?.debug); - return internalClient.getBucket(name); + return internalClient.getBucket({ name }); }, async (token: string, name: string, options?: AzionClientOptions): Promise => { // NOTE: This is a temporary solution because the API does not provide an endpoint @@ -129,20 +162,29 @@ const getBucketMethod = createInternalOrExternalMethod( return { ...bucket, - getObjects: (params: AzionObjectCollectionParams): Promise => - getObjectsMethod(token, name, params, options), - getObjectByKey: (objectKey: string): Promise => - getObjectByKeyMethod(token, name, objectKey, options), - createObject: (objectKey: string, file: string): Promise => - createObjectMethod(token, name, objectKey, file, options), - updateObject: (objectKey: string, file: string): Promise => - updateObjectMethod(token, name, objectKey, file, options), - deleteObject: (objectKey: string): Promise => - deleteObjectMethod(token, name, objectKey, options), + getObjects: ({ params }: { params: AzionObjectCollectionParams }): Promise => + getObjectsMethod(token, name, params), + getObjectByKey: ({ key }: { key: string }): Promise => + getObjectByKeyMethod(token, name, key), + createObject: ({ key, content }: { key: string; content: string }): Promise => + createObjectMethod(token, name, key, content), + updateObject: ({ key, content }: { key: string; content: string }): Promise => + updateObjectMethod(token, name, key, content), + deleteObject: ({ key }: { key: string }): Promise => + deleteObjectMethod(token, name, key), }; }, ); +/** + * Updates an existing bucket. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} name - Name of the bucket to update. + * @param {string} edge_access - New edge access configuration for the bucket. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} The updated bucket or null if update failed. + */ export const updateBucketMethod = async ( token: string, name: string, @@ -153,276 +195,353 @@ export const updateBucketMethod = async ( if (apiResponse) { return { ...apiResponse.data, - getObjects: (params: AzionObjectCollectionParams): Promise => - getObjectsMethod(token, name, params, options), - getObjectByKey: (objectKey: string): Promise => - getObjectByKeyMethod(token, name, objectKey, options), - createObject: (objectKey: string, file: string): Promise => - createObjectMethod(token, name, objectKey, file, options), - updateObject: (objectKey: string, file: string): Promise => - updateObjectMethod(token, name, objectKey, file, options), - deleteObject: (objectKey: string): Promise => - deleteObjectMethod(token, name, objectKey, options), + getObjects: ({ params }: { params: AzionObjectCollectionParams }): Promise => + getObjectsMethod(token, name, params), + getObjectByKey: ({ key }: { key: string }): Promise => + getObjectByKeyMethod(token, name, key), + createObject: ({ key, content }: { key: string; content: string }): Promise => + createObjectMethod(token, name, key, content), + updateObject: ({ key, content }: { key: string; content: string }): Promise => + updateObjectMethod(token, name, key, content), + deleteObject: ({ key }: { key: string }): Promise => + deleteObjectMethod(token, name, key), }; } return null; }; +/** + * Retrieves a list of objects in a specific bucket. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} bucket - Name of the bucket to retrieve objects from. + * @param {AzionObjectCollectionParams} [params] - Optional parameters for object collection. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} Array of bucket objects or null if retrieval failed. + */ const getObjectsMethod = createInternalOrExternalMethod( async ( token: string, - bucketName: string, + bucket: string, params?: AzionObjectCollectionParams, options?: AzionClientOptions, ): Promise => { const internalClient = new InternalStorageClient(token, options?.debug); - internalClient.name = bucketName; - return internalClient.getObjects(params); + internalClient.name = bucket; + return internalClient.getObjects({ params }); }, async ( token: string, - bucketName: string, + bucket: string, params?: AzionObjectCollectionParams, options?: AzionClientOptions, ): Promise => { - const apiResponse = await getObjects(resolveToken(token), bucketName, params, options?.debug); + const apiResponse = await getObjects(resolveToken(token), bucket, params, options?.debug); return apiResponse?.results ?? null; }, ); +/** + * Retrieves an object from a specific bucket by its key. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} bucket - Name of the bucket containing the object. + * @param {string} key - Key of the object to retrieve. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} The retrieved object or null if not found. + */ const getObjectByKeyMethod = createInternalOrExternalMethod( async ( token: string, - bucketName: string, - objectKey: string, + bucket: string, + key: string, options?: AzionClientOptions, ): Promise => { const internalClient = new InternalStorageClient(token, options?.debug); - internalClient.name = bucketName; - return internalClient.getObjectByKey(objectKey); + internalClient.name = bucket; + return internalClient.getObjectByKey({ key }); }, async ( token: string, - bucketName: string, - objectKey: string, + bucket: string, + key: string, options?: AzionClientOptions, ): Promise => { - const apiResponse = await getObjectByKey(resolveToken(token), bucketName, objectKey, resolveDebug(options?.debug)); - return apiResponse ? { key: objectKey, content: apiResponse } : null; + const apiResponse = await getObjectByKey(resolveToken(token), bucket, key, resolveDebug(options?.debug)); + return apiResponse ? { key: key, content: apiResponse } : null; }, ); +/** + * Creates a new object in a specific bucket. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} bucket - Name of the bucket to create the object in. + * @param {string} key - Key (name) of the object to create. + * @param {string} content - Content of the content to upload. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} The created object or null if creation failed. + */ const createObjectMethod = createInternalOrExternalMethod( async ( token: string, - bucketName: string, - objectKey: string, - file: string, + bucket: string, + key: string, + content: string, options?: AzionClientOptions, ): Promise => { const internalClient = new InternalStorageClient(token, options?.debug); - internalClient.name = bucketName; - return internalClient.createObject(objectKey, file); + internalClient.name = bucket; + return internalClient.createObject({ key, content }); }, async ( token: string, - bucketName: string, - objectKey: string, - file: string, + bucket: string, + key: string, + content: string, options?: AzionClientOptions, ): Promise => { - const apiResponse = await postObject( - resolveToken(token), - bucketName, - objectKey, - file, - resolveDebug(options?.debug), - ); - return apiResponse ? { key: apiResponse.data.object_key, content: file, state: apiResponse.state } : null; + const apiResponse = await postObject(resolveToken(token), bucket, key, content, resolveDebug(options?.debug)); + return apiResponse ? { key: apiResponse.data.object_key, content: content, state: apiResponse.state } : null; }, ); +/** + * Updates an existing object in a specific bucket. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} bucket - Name of the bucket containing the object. + * @param {string} key - Key of the object to update. + * @param {string} content - New content of the content. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} The updated object or null if update failed. + */ const updateObjectMethod = createInternalOrExternalMethod( async ( token: string, - bucketName: string, - objectKey: string, - file: string, + bucket: string, + key: string, + content: string, options?: AzionClientOptions, ): Promise => { const internalClient = new InternalStorageClient(token, options?.debug); - internalClient.name = bucketName; - return internalClient.updateObject(objectKey, file); + internalClient.name = bucket; + return internalClient.updateObject({ key, content }); }, async ( token: string, - bucketName: string, - objectKey: string, - file: string, + bucket: string, + key: string, + content: string, options?: AzionClientOptions, ): Promise => { - const apiResponse = await putObject(resolveToken(token), bucketName, objectKey, file, resolveDebug(options?.debug)); - return apiResponse ? { key: apiResponse.data.object_key, content: file, state: apiResponse.state } : null; + const apiResponse = await putObject(resolveToken(token), bucket, key, content, resolveDebug(options?.debug)); + return apiResponse ? { key: apiResponse.data.object_key, content: content, state: apiResponse.state } : null; }, ); +/** + * Deletes an object from a specific bucket. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} bucket - Name of the bucket containing the object. + * @param {string} key - Key of the object to delete. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} Confirmation of deletion or null if deletion failed. + */ const deleteObjectMethod = createInternalOrExternalMethod( async ( token: string, - bucketName: string, - objectKey: string, + bucket: string, + key: string, options?: AzionClientOptions, ): Promise => { const internalClient = new InternalStorageClient(token, options?.debug); - internalClient.name = bucketName; - return internalClient.deleteObject(objectKey); + internalClient.name = bucket; + return internalClient.deleteObject({ key }); }, async ( token: string, - bucketName: string, - objectKey: string, + bucket: string, + key: string, options?: AzionClientOptions, ): Promise => { - const apiResponse = await deleteObject(resolveToken(token), bucketName, objectKey, resolveDebug(options?.debug)); - return apiResponse ? { key: objectKey, state: apiResponse.state } : null; + const apiResponse = await deleteObject(resolveToken(token), bucket, key, resolveDebug(options?.debug)); + return apiResponse ? { key: key, state: apiResponse.state } : null; }, ); /** * Creates a new bucket. * - * @param {string} name - Name of the bucket to create. - * @param {string} edge_access - Edge access configuration for the bucket. - * @param {boolean} [debug=false] - Enable debug mode for detailed logging. - * @returns {Promise} The created bucket object or null if creation failed. + * @param {Object} params - Parameters for creating a bucket. + * @param {string} params.name - Name of the new bucket. + * @param {string} params.edge_access - Edge access configuration for the bucket. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise} The created bucket or null if creation failed. * * @example - * const bucket = await createBucket('my-new-bucket', 'public', true); - * if (bucket) { - * console.log(`Bucket created with name: ${bucket.name}`); + * const newBucket = await createBucket({ name: 'my-new-bucket', edge_access: 'public', options: { debug: true } }); + * if (newBucket) { + * console.log(`Bucket created with name: ${newBucket.name}`); * } else { * console.error('Failed to create bucket'); * } */ -const createBucketWrapper = ( - name: string, - edge_access: string, - options?: AzionClientOptions, -): Promise => +const createBucketWrapper = ({ + name, + edge_access, + options, +}: { + name: string; + edge_access: string; + options?: AzionClientOptions; +}): Promise => createBucketMethod(resolveToken(), name, edge_access, { ...options, debug: resolveDebug(options?.debug) }); /** * Deletes a bucket by its name. * - * @param {string} name - Name of the bucket to delete. - * @param {boolean} [debug=false] - Enable debug mode for detailed logging. - * @returns {Promise} Object confirming deletion or null if deletion failed. + * @param {Object} params - Parameters for deleting a bucket. + * @param {string} params.name - Name of the bucket to delete. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise} Confirmation of deletion or null if deletion failed. * * @example - * const result = await deleteBucket('my-bucket', true); + * const result = await deleteBucket({ name: 'my-bucket', options: { debug: true } }); * if (result) { * console.log(`Bucket ${result.name} deleted successfully`); * } else { * console.error('Failed to delete bucket'); * } */ -const deleteBucketWrapper = (name: string, options?: AzionClientOptions): Promise => +const deleteBucketWrapper = ({ + name, + options, +}: { + name: string; + options?: AzionClientOptions; +}): Promise => deleteBucketMethod(resolveToken(), name, { ...options, debug: resolveDebug(options?.debug) }); /** * Retrieves a list of buckets with optional filtering and pagination. * - * @param {AzionBucketCollectionParams} [params] - Optional parameters for filtering and pagination. - * @param {boolean} [debug=false] - Enable debug mode for detailed logging. - * @returns {Promise} Array of bucket objects or null if retrieval failed. + * @param {Object} params - Parameters for retrieving buckets. + * @param {AzionBucketCollectionParams} [params.params] - Optional parameters for filtering and pagination. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise} Array of bucket objects or null if retrieval failed. * * @example - * const buckets = await getBuckets({ page: 1, page_size: 10 }, true); + * const buckets = await getBuckets({ params: { limit: 10, offset: 0 }, options: { debug: true } }); * if (buckets) { * console.log(`Retrieved ${buckets.length} buckets`); * } else { * console.error('Failed to retrieve buckets'); * } */ -const getBucketsWrapper = ( - params?: AzionBucketCollectionParams, - clientOptions?: AzionClientOptions, -): Promise => - getBucketsMethod(resolveToken(), params, { ...clientOptions, debug: resolveDebug(clientOptions?.debug) }); +const getBucketsWrapper = ({ + params, + options, +}: { + params?: AzionBucketCollectionParams; + options?: AzionClientOptions; +}): Promise => + getBucketsMethod(resolveToken(), params, { ...options, debug: resolveDebug(options?.debug) }); /** * Retrieves a bucket by its name. * - * @param {string} name - Name of the bucket to retrieve. - * @param {boolean} [debug=false] - Enable debug mode for detailed logging. - * @returns {Promise} The retrieved bucket or null if not found. + * @param {Object} params - Parameters for retrieving a bucket. + * @param {string} params.name - Name of the bucket to retrieve. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise} The retrieved bucket or null if not found. * * @example - * const bucket = await getBucket('my-bucket', true); + * const bucket = await getBucket({ name: 'my-bucket', options: { debug: true } }); * if (bucket) { * console.log(`Retrieved bucket: ${bucket.name}`); * } else { * console.error('Bucket not found'); * } */ -const getBucketWrapper = (name: string, options?: AzionClientOptions): Promise => +const getBucketWrapper = ({ + name, + options, +}: { + name: string; + options?: AzionClientOptions; +}): Promise => getBucketMethod(resolveToken(), name, { ...options, debug: resolveDebug(options?.debug) }); /** * Updates an existing bucket. * - * @param {string} name - Name of the bucket to update. - * @param {string} edge_access - New edge access configuration for the bucket. - * @param {boolean} [debug=false] - Enable debug mode for detailed logging. - * @returns {Promise} The updated bucket or null if update failed. + * @param {Object} params - Parameters for updating a bucket. + * @param {string} params.name - Name of the bucket to update. + * @param {string} params.edge_access - New edge access configuration for the bucket. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise} The updated bucket or null if update failed. * * @example - * const updatedBucket = await updateBucket('my-bucket', 'private', true); + * const updatedBucket = await updateBucket({ name: 'my-bucket', edge_access: 'private', options: { debug: true } }); * if (updatedBucket) { * console.log(`Bucket updated: ${updatedBucket.name}`); * } else { * console.error('Failed to update bucket'); * } */ -const updateBucketWrapper = ( - name: string, - edge_access: string, - options?: AzionClientOptions, -): Promise => +const updateBucketWrapper = ({ + name, + edge_access, + options, +}: { + name: string; + edge_access: string; + options?: AzionClientOptions; +}): Promise => updateBucketMethod(resolveToken(), name, edge_access, { ...options, debug: resolveDebug(options?.debug) }); /** * Retrieves a list of objects in a specific bucket. * - * @param {string} bucketName - Name of the bucket to retrieve objects from. - * @param {AzionObjectCollectionParams} [params] - Optional parameters for object collection. - * @param {AzionClientOptions} [clientOptions] - Optional client configuration. - * @returns {Promise} Array of bucket objects or null if retrieval failed. + * @param {Object} params - Parameters for retrieving objects. + * @param {string} params.bucket - Name of the bucket to retrieve objects from. + * @param {AzionObjectCollectionParams} [params.params] - Optional parameters for object collection. + * @param {number} [params.params.max_object_count=10000] - Maximum number of objects to retrieve. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise} Array of bucket objects or null if retrieval failed. * * @example - * const objects = await getObjects('my-bucket', { max_object_count: 50 }, { debug: true }); + * const objects = await getObjects({ bucket: 'my-bucket', params: { max_object_count: 50 }, options: { debug: true } }); * if (objects) { * console.log(`Retrieved ${objects.length} objects from the bucket`); * } else { * console.error('Failed to retrieve objects'); * } */ -const getObjectsWrapper = ( - bucketName: string, - params?: AzionObjectCollectionParams, - options?: AzionClientOptions, -): Promise => getObjectsMethod(resolveToken(), bucketName, params, options); +const getObjectsWrapper = ({ + bucket, + params, + options, +}: { + bucket: string; + params?: AzionObjectCollectionParams; + options?: AzionClientOptions; +}): Promise => getObjectsMethod(resolveToken(), bucket, params, options); + /** * Creates a new object in a specific bucket. * - * @param {string} bucketName - Name of the bucket to create the object in. - * @param {string} objectKey - Key (name) of the object to create. - * @param {string} file - Content of the file to upload. - * @param {boolean} [debug=false] - Enable debug mode for detailed logging. - * @returns {Promise} The created object or null if creation failed. + * @param {Object} params - Parameters for creating an object. + * @param {string} params.bucket - Name of the bucket to create the object in. + * @param {string} params.key - Key (name) of the object to create. + * @param {string} params.content - Content of the content to upload. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise} The created object or null if creation failed. * * @example - * const newObject = await createObject('my-bucket', 'new-file.txt', 'File content', true); + * const newObject = await createObject({ bucket: 'my-bucket', key: 'new-content.txt', content: 'content content', options: { debug: true } }); * if (newObject) { * console.log(`Object created with key: ${newObject.key}`); * console.log(`Object content: ${newObject.content}`); @@ -430,36 +549,46 @@ const getObjectsWrapper = ( * console.error('Failed to create object'); * } */ -const createObjectWrapper = ( - bucketName: string, - objectKey: string, - file: string, - options?: AzionClientOptions, -): Promise => - createObjectMethod(resolveToken(), bucketName, objectKey, file, { ...options, debug: resolveDebug(options?.debug) }); +const createObjectWrapper = ({ + bucket, + key, + content, + options, +}: { + bucket: string; + key: string; + content: string; + options?: AzionClientOptions; +}): Promise => + createObjectMethod(resolveToken(), bucket, key, content, { ...options, debug: resolveDebug(options?.debug) }); /** * Retrieves an object from a specific bucket by its key. * - * @param {string} bucketName - Name of the bucket containing the object. - * @param {string} objectKey - Key of the object to retrieve. - * @param {boolean} [debug=false] - Enable debug mode for detailed logging. - * @returns {Promise} The retrieved object or null if not found. + * @param {Object} params - Parameters for retrieving an object. + * @param {string} params.bucket - Name of the bucket containing the object. + * @param {string} params.key - Key of the object to retrieve. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise} The retrieved object or null if not found. * * @example - * const object = await getObjectByKey('my-bucket', 'file.txt', true); + * const object = await getObjectByKey({ bucket: 'my-bucket', key: 'content.txt', options: { debug: true } }); * if (object) { * console.log(`Retrieved object: ${object.key}`); * } else { * console.error('Object not found'); * } */ -const getObjectByKeyWrapper = ( - bucketName: string, - objectKey: string, - options?: AzionClientOptions, -): Promise => - getObjectByKeyMethod(resolveToken(), bucketName, objectKey, { +const getObjectByKeyWrapper = ({ + bucket, + key, + options, +}: { + bucket: string; + key: string; + options?: AzionClientOptions; +}): Promise => + getObjectByKeyMethod(resolveToken(), bucket, key, { ...options, debug: resolveDebug(options?.debug), }); @@ -467,14 +596,15 @@ const getObjectByKeyWrapper = ( /** * Updates an existing object in a specific bucket. * - * @param {string} bucketName - Name of the bucket containing the object. - * @param {string} objectKey - Key of the object to update. - * @param {string} file - New content of the file. - * @param {boolean} [debug=false] - Enable debug mode for detailed logging. - * @returns {Promise} The updated object or null if update failed. + * @param {Object} params - Parameters for updating an object. + * @param {string} params.bucket - Name of the bucket containing the object. + * @param {string} params.key - Key of the object to update. + * @param {string} params.content - New content of the content. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise} The updated object or null if update failed. * * @example - * const updatedObject = await updateObject('my-bucket', 'file.txt', 'Updated content', true); + * const updatedObject = await updateObject({ bucket: 'my-bucket', key: 'content.txt', content: 'Updated content', options: { debug: true } }); * if (updatedObject) { * console.log(`Object updated: ${updatedObject.key}`); * console.log(`New content: ${updatedObject.content}`); @@ -482,13 +612,18 @@ const getObjectByKeyWrapper = ( * console.error('Failed to update object'); * } */ -const updateObjectWrapper = ( - bucketName: string, - objectKey: string, - file: string, - options?: AzionClientOptions, -): Promise => - updateObjectMethod(resolveToken(), bucketName, objectKey, file, { +const updateObjectWrapper = ({ + bucket, + key, + content, + options, +}: { + bucket: string; + key: string; + content: string; + options?: AzionClientOptions; +}): Promise => + updateObjectMethod(resolveToken(), bucket, key, content, { ...options, debug: resolveDebug(options?.debug), }); @@ -496,25 +631,30 @@ const updateObjectWrapper = ( /** * Deletes an object from a specific bucket. * - * @param {string} bucketName - Name of the bucket containing the object. - * @param {string} objectKey - Key of the object to delete. - * @param {boolean} [debug=false] - Enable debug mode for detailed logging. - * @returns {Promise} Confirmation of deletion or null if deletion failed. + * @param {Object} params - Parameters for deleting an object. + * @param {string} params.bucket - Name of the bucket containing the object. + * @param {string} params.key - Key of the object to delete. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise} Confirmation of deletion or null if deletion failed. * * @example - * const result = await deleteObject('my-bucket', 'file.txt', true); + * const result = await deleteObject({ bucket: 'my-bucket', key: 'content.txt', options: { debug: true } }); * if (result) { * console.log(`Object ${result.key} deleted successfully`); * } else { * console.error('Failed to delete object'); * } */ -const deleteObjectWrapper = ( - bucketName: string, - objectKey: string, - options?: AzionClientOptions, -): Promise => - deleteObjectMethod(resolveToken(), bucketName, objectKey, { +const deleteObjectWrapper = ({ + bucket, + key, + options, +}: { + bucket: string; + key: string; + options?: AzionClientOptions; +}): Promise => + deleteObjectMethod(resolveToken(), bucket, key, { ...options, debug: resolveDebug(options?.debug), }); @@ -522,20 +662,20 @@ const deleteObjectWrapper = ( /** * Creates a Storage client with methods to interact with Azion Edge Storage. * - * @param {Partial<{ token: string; debug: boolean }>} [config] - Configuration options for the Storage client. - * @returns {StorageClient} An object with methods to interact with Storage. + * @param {Partial<{ token: string; options?: AzionClientOptions }>} [config] - Configuration options for the Storage client. + * @returns {AzionStorageClient} An object with methods to interact with Storage. * * @example - * const storageClient = createClient({ token: 'your-api-token', debug: true }); + * const storageClient = createClient({ token: 'your-api-token', options: { debug: true } }); * * // Create a new bucket - * const newBucket = await storageClient.createBucket('my-new-bucket', 'public'); + * const newBucket = await storageClient.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); * * // Get all buckets - * const allBuckets = await storageClient.getBuckets(); + * const allBuckets = await storageClient.getBuckets({ params: { page: 1, page_size: 10 } }); * * // Delete a bucket - * const deletedBucket = await storageClient.deleteBucket('my-bucket'); + * const deletedBucket = await storageClient.deleteBucket({ name: 'my-bucket' }); */ const client: CreateAzionStorageClient = ( config?: Partial<{ token: string; options?: AzionClientOptions }>, @@ -546,44 +686,49 @@ const client: CreateAzionStorageClient = ( const client: AzionStorageClient = { /** * Retrieves a list of buckets with optional filtering and pagination. - * @param {BucketCollectionParams} [params] - Optional parameters for filtering and pagination. - * @returns {Promise} Array of buckets or null if retrieval failed. + * @param {Object} params - Parameters for retrieving buckets. + * @param {AzionBucketCollectionParams} [params.params] - Optional parameters for filtering and pagination. + * @returns {Promise} Array of buckets or null if retrieval failed. */ - getBuckets: (params?: AzionBucketCollectionParams): Promise => - getBucketsMethod(tokenValue, params, { ...config, debug: debugValue }), + getBuckets: (params?: { params?: AzionBucketCollectionParams }): Promise => + getBucketsMethod(tokenValue, params?.params, { ...config, debug: debugValue }), /** * Creates a new bucket. - * @param {string} name - Name of the new bucket. - * @param {string} edge_access - Edge access configuration for the bucket. - * @returns {Promise} The created bucket or null if creation failed. + * @param {Object} params - Parameters for creating a bucket. + * @param {string} params.name - Name of the new bucket. + * @param {string} params.edge_access - Edge access configuration for the bucket. + * @returns {Promise} The created bucket or null if creation failed. */ - createBucket: (name: string, edge_access: string): Promise => + createBucket: ({ name, edge_access }: { name: string; edge_access: string }): Promise => createBucketMethod(tokenValue, name, edge_access, { ...config, debug: debugValue }), /** * Updates an existing bucket. - * @param {string} name - Name of the bucket to update. - * @param {string} edge_access - New edge access configuration for the bucket. - * @returns {Promise} The updated bucket or null if update failed. + * @param {Object} params - Parameters for updating a bucket. + * @param {string} params.name - Name of the bucket to update. + * @param {string} params.edge_access - New edge access configuration for the bucket. + * @returns {Promise} The updated bucket or null if update failed. */ - updateBucket: (name: string, edge_access: string): Promise => + updateBucket: ({ name, edge_access }: { name: string; edge_access: string }): Promise => updateBucketMethod(tokenValue, name, edge_access, { ...config, debug: debugValue }), /** * Deletes a bucket by its name. - * @param {string} name - Name of the bucket to delete. - * @returns {Promise} Confirmation of deletion or null if deletion failed. + * @param {Object} params - Parameters for deleting a bucket. + * @param {string} params.name - Name of the bucket to delete. + * @returns {Promise} Confirmation of deletion or null if deletion failed. */ - deleteBucket: (name: string): Promise => + deleteBucket: ({ name }: { name: string }): Promise => deleteBucketMethod(tokenValue, name, { ...config, debug: debugValue }), /** * Retrieves a bucket by its name. - * @param {string} name - Name of the bucket to retrieve. - * @returns {Promise} The retrieved bucket or null if not found. + * @param {Object} params - Parameters for retrieving a bucket. + * @param {string} params.name - Name of the bucket to retrieve. + * @returns {Promise} The retrieved bucket or null if not found. */ - getBucket: (name: string): Promise => + getBucket: ({ name }: { name: string }): Promise => getBucketMethod(tokenValue, name, { ...config, debug: debugValue }), } as const; diff --git a/packages/storage/src/services/api/index.ts b/packages/storage/src/services/api/index.ts index 36490a7..6b93e38 100644 --- a/packages/storage/src/services/api/index.ts +++ b/packages/storage/src/services/api/index.ts @@ -12,6 +12,14 @@ import { const BASE_URL = 'https://api.azion.com/v4/storage/buckets'; +/** + * Retrieves a list of buckets with optional filtering and pagination. + * + * @param {string} token - Authentication token for Azion API. + * @param {ApiListBucketsParams} [params] - Optional parameters for filtering and pagination. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Array of buckets or an error if retrieval failed. + */ const getBuckets = async ( token: string, params?: ApiListBucketsParams, @@ -33,6 +41,15 @@ const getBuckets = async ( } }; +/** + * Creates a new bucket. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} name - Name of the bucket to create. + * @param {string} edge_access - Edge access configuration for the bucket. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The created bucket or an error if creation failed. + */ const postBucket = async ( token: string, name: string, @@ -54,6 +71,15 @@ const postBucket = async ( } }; +/** + * Updates an existing bucket. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} name - Name of the bucket to update. + * @param {string} edge_access - New edge access configuration for the bucket. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The updated bucket or an error if update failed. + */ const patchBucket = async ( token: string, name: string, @@ -75,6 +101,14 @@ const patchBucket = async ( } }; +/** + * Deletes a bucket by its name. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} name - Name of the bucket to delete. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Confirmation of deletion or an error if deletion failed. + */ const deleteBucket = async (token: string, name: string, debug?: boolean): Promise => { try { const response = await fetch(`${BASE_URL}/${name}`, { @@ -90,6 +124,15 @@ const deleteBucket = async (token: string, name: string, debug?: boolean): Promi } }; +/** + * Retrieves a list of objects in a bucket with optional filtering. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} bucketName - Name of the bucket. + * @param {ApiListObjectsParams} [params] - Optional parameters for filtering. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Array of objects or an error if retrieval failed. + */ const getObjects = async ( token: string, bucketName: string, @@ -112,15 +155,25 @@ const getObjects = async ( } }; +/** + * Creates a new object in a bucket. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} bucketName - Name of the bucket. + * @param {string} key - Key of the object to create. + * @param {string} file - Content of the object. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The created object or an error if creation failed. + */ const postObject = async ( token: string, bucketName: string, - objectKey: string, + key: string, file: string, debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${bucketName}/objects/${objectKey}`, { + const response = await fetch(`${BASE_URL}/${bucketName}/objects/${key}`, { method: 'POST', headers: { Accept: 'application/json', @@ -138,14 +191,18 @@ const postObject = async ( } }; -const getObjectByKey = async ( - token: string, - bucketName: string, - objectKey: string, - debug?: boolean, -): Promise => { +/** + * Retrieves an object by its key. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} bucketName - Name of the bucket. + * @param {string} key - Key of the object to retrieve. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The content of the object or an error if retrieval failed. + */ +const getObjectByKey = async (token: string, bucketName: string, key: string, debug?: boolean): Promise => { try { - const response = await fetch(`${BASE_URL}/${bucketName}/objects/${objectKey}`, { + const response = await fetch(`${BASE_URL}/${bucketName}/objects/${key}`, { method: 'GET', headers: { Accept: 'application/json', Authorization: `Token ${token}` }, }); @@ -158,15 +215,25 @@ const getObjectByKey = async ( } }; +/** + * Updates an existing object in a bucket. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} bucketName - Name of the bucket. + * @param {string} key - Key of the object to update. + * @param {string} file - New content of the object. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The updated object or an error if update failed. + */ const putObject = async ( token: string, bucketName: string, - objectKey: string, + key: string, file: string, debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${bucketName}/objects/${objectKey}`, { + const response = await fetch(`${BASE_URL}/${bucketName}/objects/${key}`, { method: 'PUT', headers: { Accept: 'application/json', @@ -184,14 +251,23 @@ const putObject = async ( } }; +/** + * Deletes an object from a bucket. + * + * @param {string} token - Authentication token for Azion API. + * @param {string} bucketName - Name of the bucket. + * @param {string} key - Key of the object to delete. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Confirmation of deletion or an error if deletion failed. + */ const deleteObject = async ( token: string, bucketName: string, - objectKey: string, + key: string, debug?: boolean, ): Promise => { try { - const response = await fetch(`${BASE_URL}/${bucketName}/objects/${objectKey}`, { + const response = await fetch(`${BASE_URL}/${bucketName}/objects/${key}`, { method: 'DELETE', headers: { Accept: 'application/json', Authorization: `Token ${token}` }, }); diff --git a/packages/storage/src/services/runtime/index.ts b/packages/storage/src/services/runtime/index.ts index 51d7f15..b33cd01 100644 --- a/packages/storage/src/services/runtime/index.ts +++ b/packages/storage/src/services/runtime/index.ts @@ -40,10 +40,11 @@ export class InternalStorageClient implements AzionBucket { /** * Retrieves a bucket by name. * - * @param {string} name The name of the bucket. + * @param {Object} params - Parameters for retrieving a bucket. + * @param {string} params.name - The name of the bucket. * @returns {Promise} The bucket object or null if not found. */ - async getBucket(name: string): Promise { + async getBucket({ name }: { name: string }): Promise { this.initializeStorage(name); if (this.storage) { this.name = name; @@ -66,10 +67,11 @@ export class InternalStorageClient implements AzionBucket { * * Uses retry with exponential backoff to handle eventual consistency delays. * - * @param {AzionObjectCollectionParams} params - Parameters for object collection. + * @param {Object} params - Parameters for object collection. + * @param {AzionObjectCollectionParams} [params.params] - Parameters for object collection. * @returns {Promise} The list of objects or null if an error occurs. */ - async getObjects(params?: AzionObjectCollectionParams): Promise { + async getObjects({ params }: { params?: AzionObjectCollectionParams }): Promise { this.initializeStorage(this.name); try { const objectList = await retryWithBackoff(() => this.storage!.list()); @@ -95,19 +97,20 @@ export class InternalStorageClient implements AzionBucket { * * Uses retry with exponential backoff to handle eventual consistency delays. * - * @param {string} objectKey The key of the object to retrieve. + * @param {Object} params - Parameters for retrieving an object. + * @param {string} params.key - The key of the object to retrieve. * @returns {Promise} The object or null if an error occurs. */ - async getObjectByKey(objectKey: string): Promise { + async getObjectByKey({ key }: { key: string }): Promise { this.initializeStorage(this.name); try { - const storageObject = await retryWithBackoff(() => this.storage!.get(objectKey)); + const storageObject = await retryWithBackoff(() => this.storage!.get(key)); const arrayBuffer = await storageObject.arrayBuffer(); const decoder = new TextDecoder(); const content = decoder.decode(arrayBuffer); return { state: 'executed-runtime', - key: removeLeadingSlash(objectKey), + key: removeLeadingSlash(key), size: storageObject.contentLength, content: content, content_type: storageObject.metadata.get('content-type'), @@ -123,27 +126,32 @@ export class InternalStorageClient implements AzionBucket { * * Uses retry with exponential backoff to handle eventual consistency delays. * - * @param {string} objectKey The key of the object to create. - * @param {string} content The content of the object. - * @param {{ content_type?: string }} [options] Optional metadata for the object. + * @param {Object} params - Parameters for creating an object. + * @param {string} params.key - The key of the object to create. + * @param {string} params.content - The content of the object. + * @param {{ content_type?: string }} [params.options] - Optional metadata for the object. * @returns {Promise} The created object or null if an error occurs. */ - async createObject( - objectKey: string, - content: string, - options?: { content_type?: string }, - ): Promise { + async createObject({ + key, + content, + options, + }: { + key: string; + content: string; + options?: { content_type?: string }; + }): Promise { this.initializeStorage(this.name); try { const contentBuffer = new TextEncoder().encode(content); await retryWithBackoff(() => - this.storage!.put(objectKey, contentBuffer, { + this.storage!.put(key, contentBuffer, { 'content-type': options?.content_type, }), ); return { state: 'executed-runtime', - key: removeLeadingSlash(objectKey), + key: removeLeadingSlash(key), size: contentBuffer.byteLength, content_type: options?.content_type, content: content, @@ -159,17 +167,22 @@ export class InternalStorageClient implements AzionBucket { * * Uses retry with exponential backoff to handle eventual consistency delays. * - * @param {string} objectKey The key of the object to update. - * @param {string} content The new content of the object. - * @param {{ content_type?: string }} [options] Optional metadata for the object. + * @param {Object} params - Parameters for updating an object. + * @param {string} params.key - The key of the object to update. + * @param {string} params.content - The new content of the object. + * @param {{ content_type?: string }} [params.options] - Optional metadata for the object. * @returns {Promise} The updated object or null if an error occurs. */ - async updateObject( - objectKey: string, - content: string, - options?: { content_type?: string }, - ): Promise { - return this.createObject(objectKey, content, options); + async updateObject({ + key, + content, + options, + }: { + key: string; + content: string; + options?: { content_type?: string }; + }): Promise { + return this.createObject({ key, content, options }); } /** @@ -177,14 +190,15 @@ export class InternalStorageClient implements AzionBucket { * * Uses retry with exponential backoff to handle eventual consistency delays. * - * @param {string} objectKey The key of the object to delete. + * @param {Object} params - Parameters for deleting an object. + * @param {string} params.key - The key of the object to delete. * @returns {Promise} Confirmation of deletion or null if an error occurs. */ - async deleteObject(objectKey: string): Promise { + async deleteObject({ key }: { key: string }): Promise { this.initializeStorage(this.name); try { - await retryWithBackoff(() => this.storage!.delete(objectKey)); - return { key: removeLeadingSlash(objectKey), state: 'executed-runtime' }; + await retryWithBackoff(() => this.storage!.delete(key)); + return { key: removeLeadingSlash(key), state: 'executed-runtime' }; } catch (error) { if (this.debug) console.error('Error deleting object:', error); return null; diff --git a/packages/storage/src/types.ts b/packages/storage/src/types.ts index 89ce2af..7775b30 100644 --- a/packages/storage/src/types.ts +++ b/packages/storage/src/types.ts @@ -4,19 +4,19 @@ export interface AzionBucket { name: string; edge_access: string; state?: 'executed' | 'executed-runtime' | 'pending'; - getObjects: (params: AzionObjectCollectionParams) => Promise; - getObjectByKey: (objectKey: string) => Promise; - createObject: ( - objectKey: string, - file: string, - options?: { content_type?: string }, - ) => Promise; - updateObject: ( - objectKey: string, - file: string, - options?: { content_type?: string }, - ) => Promise; - deleteObject: (objectKey: string) => Promise; + getObjects: (params: { params: AzionObjectCollectionParams }) => Promise; + getObjectByKey: (params: { key: string }) => Promise; + createObject: (params: { + key: string; + content: string; + options?: { content_type?: string }; + }) => Promise; + updateObject: (params: { + key: string; + content: string; + options?: { content_type?: string }; + }) => Promise; + deleteObject: (params: { key: string }) => Promise; } export interface AzionBucketObject { @@ -37,12 +37,13 @@ export interface AzionDeletedBucket { name: string; state?: 'executed' | 'executed-runtime' | 'pending'; } + export interface AzionStorageClient { - getBuckets: (options?: AzionBucketCollectionParams) => Promise; - createBucket: (name: string, edge_access: string) => Promise; - updateBucket: (name: string, edge_access: string) => Promise; - deleteBucket: (name: string) => Promise; - getBucket: (name: string) => Promise; + getBuckets: (params?: { params?: AzionBucketCollectionParams }) => Promise; + createBucket: (params: { name: string; edge_access: string }) => Promise; + updateBucket: (params: { name: string; edge_access: string }) => Promise; + deleteBucket: (params: { name: string }) => Promise; + getBucket: (params: { name: string }) => Promise; } export type AzionBucketCollectionParams = { From eabb1abc4331987f8a27fd4ca4b36ad0e4083b1b Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Thu, 29 Aug 2024 15:21:45 -0300 Subject: [PATCH 03/45] docs: update cookie docs --- packages/cookies/README.md | 7 ++--- packages/cookies/src/getCookie/index.ts | 42 ++++++++++++++++++++++++- packages/cookies/src/index.ts | 4 +-- packages/cookies/src/setCookie/index.ts | 33 +++++++++++++++++++ 4 files changed, 79 insertions(+), 7 deletions(-) diff --git a/packages/cookies/README.md b/packages/cookies/README.md index 35da453..992db64 100644 --- a/packages/cookies/README.md +++ b/packages/cookies/README.md @@ -46,8 +46,7 @@ console.log(myCookie); // Outputs the value of 'my-cookie' **TypeScript:** ```typescript -import { getCookie } from 'azion/cookies'; -import { CookiePrefix } from 'azion/cookies/types'; +import { getCookie, CookiePrefix } from 'azion/cookies'; const myCookie: string | undefined = getCookie(request, 'my-cookie'); const secureCookie: string | undefined = getCookie(request, 'my-cookie', 'secure'); @@ -62,7 +61,7 @@ console.log(secureCookie); // Outputs the value of '__Secure-my-cookie' ```javascript import { setCookie } from 'azion/cookies'; -setCookie(response, 'my-cookie', 'cookie-value', { maxAge: 3600 }); +const res = setCookie(response, 'my-cookie', 'cookie-value', { maxAge: 3600 }); ``` **TypeScript:** @@ -72,7 +71,7 @@ import { setCookie } from 'azion/cookies'; import { CookieOptions } from 'azion/cookies/types'; const options: CookieOptions = { maxAge: 3600 }; -setCookie(response, 'my-cookie', 'cookie-value', options); +const res = setCookie(response, 'my-cookie', 'cookie-value', options); ``` ## API Reference diff --git a/packages/cookies/src/getCookie/index.ts b/packages/cookies/src/getCookie/index.ts index 550192b..8f62128 100644 --- a/packages/cookies/src/getCookie/index.ts +++ b/packages/cookies/src/getCookie/index.ts @@ -2,11 +2,51 @@ import { type CookiePrefix } from '../common/types'; import { parseCookie } from '../utils/parse'; export interface GetCookie { + /** + * Retrieves a cookie value from the request headers. + * + * @param {Request} req - The HTTP request object. + * @param {string} [key] - The key of the cookie to retrieve. + * @returns {string | undefined | Record} The cookie value or an object of all cookies if no key is provided. + */ (req: Request, key?: string): string | undefined | Record; + + /** + * Retrieves a cookie value from the request headers with a prefix. + * + * @param {Request} req - The HTTP request object. + * @param {string} key - The key of the cookie to retrieve. + * @param {CookiePrefix} prefixOptions - The prefix options for the cookie key. + * @returns {string | undefined | Record} The cookie value or an object of all cookies if no key is provided. + */ (req: Request, key: string, prefixOptions: CookiePrefix): string | undefined | Record; } -export const getCookie: GetCookie = (request, key?, prefix?: CookiePrefix) => { +/** + * Retrieves a cookie value from the request headers. + * + * @param {Request} request - The HTTP request object. + * @param {string} [key] - The key of the cookie to retrieve. + * @param {CookiePrefix} [prefix] - The prefix options for the cookie key. + * @returns {string | undefined | Record} The cookie value or an object of all cookies if no key is provided. + * + * @example + * // Retrieve a specific cookie + * const cookieValue = getCookie(request, 'session_id'); + * + * @example + * // Retrieve a specific cookie with a prefix + * const cookieValue = getCookie(request, 'session_id', 'user'); + * + * @example + * // Retrieve all cookies + * const allCookies = getCookie(request); + */ +export const getCookie: GetCookie = ( + request: Request, + key?: string, + prefix?: CookiePrefix, +): string | undefined | Record => { if (!(request instanceof Request)) { return undefined; } diff --git a/packages/cookies/src/index.ts b/packages/cookies/src/index.ts index 60dbb4c..2dd22a9 100644 --- a/packages/cookies/src/index.ts +++ b/packages/cookies/src/index.ts @@ -1,12 +1,12 @@ import { getCookie } from './getCookie'; -import { setCookie } from './setCookie'; +import { CookieOptions, setCookie } from './setCookie'; const cookies = { getCookie, setCookie, }; -export { getCookie, setCookie }; +export { CookieOptions, getCookie, setCookie }; export default cookies; diff --git a/packages/cookies/src/setCookie/index.ts b/packages/cookies/src/setCookie/index.ts index 9a6faed..be4c8f0 100644 --- a/packages/cookies/src/setCookie/index.ts +++ b/packages/cookies/src/setCookie/index.ts @@ -11,9 +11,35 @@ export type CookieOptions = { }; export interface SetCookie { + /** + * Sets a cookie in the response headers. + * + * @param {Response} res - The HTTP response object. + * @param {string} key - The key of the cookie to set. + * @param {string} value - The value of the cookie to set. + * @param {CookieOptions} [options] - Optional settings for the cookie. + * @returns {Response} The modified response object with the Set-Cookie header. + */ (res: Response, key: string, value: string, options?: CookieOptions): Response; } +/** + * Sets a cookie in the response headers. + * + * @param {Response} response - The HTTP response object. + * @param {string} key - The key of the cookie to set. + * @param {string} value - The value of the cookie to set. + * @param {CookieOptions} [options] - Optional settings for the cookie. + * @returns {Response} The modified response object with the Set-Cookie header. + * + * @example + * // Set a simple cookie + * setCookie(response, 'session_id', 'abc123'); + * + * @example + * // Set a cookie with options + * setCookie(response, 'session_id', 'abc123', { httpOnly: true, secure: true }); + */ export const setCookie: SetCookie = (response, key, value, options) => { if (!(response instanceof Response)) { throw new Error('response is not an instance of Response'); @@ -68,6 +94,13 @@ export const setCookie: SetCookie = (response, key, value, options) => { return response; }; +/** + * Modifies the cookie key based on the prefix options. + * + * @param {string} key - The original cookie key. + * @param {CookieOptions} [options] - Optional settings for the cookie. + * @returns {string} The modified cookie key with the appropriate prefix. + */ const changeKeyOnPrefix = (key: string, options?: CookieOptions) => { let cookieKey = key; if (options?.prefix === 'secure') { From 6039ff1874898c3bb3f3b79a4bb286423c504da6 Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Thu, 29 Aug 2024 15:43:58 -0300 Subject: [PATCH 04/45] docs: sql docs --- packages/sql/README.MD | 278 ++++++++++++++------- packages/sql/src/services/api/index.ts | 36 +++ packages/sql/src/services/runtime/index.ts | 18 +- 3 files changed, 233 insertions(+), 99 deletions(-) diff --git a/packages/sql/README.MD b/packages/sql/README.MD index ae332fe..308d5b4 100644 --- a/packages/sql/README.MD +++ b/packages/sql/README.MD @@ -22,16 +22,24 @@ Azion Edge SQL Client provides a simple interface to interact with the Azion Edg - [API Reference](#api-reference) - [`createDatabase`](#createdatabase) - [`deleteDatabase`](#deletedatabase) - - [`getDatabaseById`](#getdatabasebyid) - - [`getDatabases`](#getdatabases) - [`getDatabase`](#getdatabase) + - [`getDatabases`](#getdatabases) + - [`useQuery`](#usequery) + - [`useExecute`](#useexecute) - [`createClient`](#createclient) - [Types](#types) - [`ClientConfig`](#clientconfig) - [`AzionSQLClient`](#azionsqlclient) - [`AzionDatabase`](#aziondatabase) - [`DeletedAzionDatabase`](#deletedaziondatabase) - - [`Query`](#query) + - [`QueryResult`](#queryresult) + - [`AzionClientOptions`](#azionclientoptions) + - [`AzionDatabaseCollectionOptions`](#aziondatabasecollectionoptions) + - [`AzionQueryResponse`](#azionqueryresponse) + - [`NonSelectQueryResult`](#nonselectqueryresult) + - [`AzionQueryExecutionInfo`](#azionqueryexecutioninfo) + - [`AzionQueryExecutionParams`](#azionqueryexecutionparams) + - [`AzionQueryParams`](#azionqueryparams) - [Contributing](#contributing) ## Installation @@ -100,8 +108,7 @@ if (database) { **TypeScript:** ```typescript -import { createDatabase } from 'azion/sql'; -import { AzionDatabase } from 'azion/sql'; +import { createDatabase, AzionDatabase } from 'azion/sql'; const database: AzionDatabase | null = await createDatabase('my-new-database', { debug: true }); if (database) { @@ -129,8 +136,7 @@ if (result) { **TypeScript:** ```typescript -import { deleteDatabase } from 'azion/sql'; -import { AzionDeletedDatabase } from 'azion/sql'; +import { deleteDatabase, AzionDeletedDatabase } from 'azion/sql'; const result: AzionDeletedDatabase | null = await deleteDatabase(123, { debug: true }); if (result) { @@ -158,8 +164,7 @@ if (database) { **TypeScript:** ```typescript -import { getDatabase } from 'azion/sql'; -import { AzionDatabase } from 'azion/sql'; +import { getDatabase, AzionDatabase } from 'azion/sql'; const database: AzionDatabase | null = await getDatabase('my-db', { debug: true }); if (database) { @@ -187,8 +192,7 @@ if (databases) { **TypeScript:** ```typescript -import { getDatabases } from 'azion/sql'; -import { AzionDatabase } from 'azion/sql'; +import { getDatabases, AzionDatabase } from 'azion/sql'; const databases: AzionDatabase[] | null = await getDatabases({ page: 1, page_size: 10 }, { debug: true }); if (databases) { @@ -216,8 +220,7 @@ if (result) { **TypeScript:** ```typescript -import { useQuery } from 'azion/sql'; -import { AzionQueryResponse } from 'azion/sql'; +import { useQuery, AzionQueryResponse } from 'azion/sql'; const result: AzionQueryResponse | null = await useQuery('my-db', ['SELECT * FROM users'], { debug: true }); if (result) { @@ -234,75 +237,30 @@ if (result) { ```javascript import { useExecute } from 'azion/sql'; -await useExecute('my-db', [`INSERT INTO users (id, name) VALUES (1, 'John Doe')`], { debug: true }); -console.log(`Executed`); -``` - -**TypeScript:** - -```typescript -import { useExecute } from 'azion/sql'; - -await useExecute('my-db', [`INSERT INTO users (id, name) VALUES (1, 'John Doe')`], { debug: true }); -console.log(`Executed`); -``` - -### Using Client - -**JavaScript:** - -```javascript -import { createClient } from 'azion/sql'; - -const client = createClient({ token: 'your-api-token', { debug: true } }); - -const newDatabase = await client.createDatabase('my-new-db'); -if (newDatabase) { - console.log(`Database created with ID: ${newDatabase.id}`); -} - -const allDatabases = await client.getDatabases(); -if (allDatabases) { - console.log(`Retrieved ${allDatabases.length} databases`); -} - -const queryResult = await newDatabase.query(['SELECT * FROM users']); -if (queryResult) { - console.log(`Query executed. Rows returned: ${queryResult.rows.length}`); +const result = await useExecute('my-db', ['INSERT INTO users (name) VALUES ("John")'], { debug: true }); +if (result?.state === 'executed') { + console.log('Executed with success'); +} else { + console.error('Execution failed'); } ``` **TypeScript:** ```typescript -import { createClient } from 'azion/sql'; -import { AzionSQLClient, AzionDatabase, AzionQueryResponse } from 'azion/sql'; - -const client: AzionSQLClient = createClient({ token: 'your-api-token', { debug: true } }); - -const newDatabase: AzionDatabase | null = await client.createDatabase('my-new-db'); -if (newDatabase) { - console.log(`Database created with ID: ${newDatabase.id}`); -} - -const databaseResult: AzionDatabase | null = await client.getDatabase('my-new-db'); -if (databaseResult) { - console.log(databaseResult); -} - -const listTablesResult: AzionQueryResponse | null = await databaseResult.listTables(); - -if (listTablesResult) { - console.log(listTablesResult); -} +import { useExecute, AzionQueryResponse } from 'azion/sql'; -const queryResult: AzionQueryResponse | null = await newDatabase.query(['SELECT * FROM users'], { debug: true }); -if (queryResult) { - console.log(`Query executed. Rows returned: ${queryResult.rows.length}`); +const result: AzionQueryResponse | null = await useExecute('my-db', ['INSERT INTO users (name) VALUES ("John")'], { + debug: true, +}); +if (result?.state === 'executed') { + console.log('Executed with success'); +} else { + console.error('Execution failed'); } ``` -### List Tables +#### List Tables **JavaScript:** @@ -321,11 +279,9 @@ if (result) { **TypeScript:** ```typescript -import { listTables } from 'azion/sql'; -import type { AzionQueryResponse } from 'azion/sql'; - -const result: AzionQueryResponse = await listTables('my-db', { debug: true }); +import { listTables, AzionQueryResponse } from 'azion/sql'; +const result: AzionQueryResponse | null = await listTables('my-db', { debug: true }); if (result) { console.log(result); } else { @@ -342,12 +298,23 @@ Creates a new database. **Parameters:** - `name: string` - Name of the new database. -- `debug?: boolean` - Enable debug mode for detailed logging. +- `options?: AzionClientOptions` - Optional parameters for the creation. **Returns:** - `Promise` - The created database object or null if creation failed. +**Example:** + +```javascript +const newDatabase = await sqlClient.createDatabase('my-new-db'); +if (newDatabase) { + console.log(`Database created with ID: ${newDatabase.id}`); +} else { + console.error('Failed to create database'); +} +``` + ### `deleteDatabase` Deletes a database by its ID. @@ -355,12 +322,23 @@ Deletes a database by its ID. **Parameters:** - `id: number` - ID of the database to delete. -- `debug?: boolean` - Enable debug mode for detailed logging. +- `options?: AzionClientOptions` - Optional parameters for the deletion. **Returns:** - `Promise` - Object confirming deletion or null if deletion failed. +**Example:** + +```javascript +const result = await sqlClient.deleteDatabase(123); +if (result) { + console.log(`Database ${result.id} deleted successfully`); +} else { + console.error('Failed to delete database'); +} +``` + ### `getDatabase` Retrieves a database by its Name. @@ -368,13 +346,23 @@ Retrieves a database by its Name. **Parameters:** - `name: string` - Name of the database to retrieve. -- `options?: OptionsParams` - Object options params. - - `debug?: boolean` - Enable debug mode for detailed logging. +- `options?: AzionClientOptions` - Optional parameters for the retrieval. **Returns:** - `Promise` - The retrieved database object or null if not found. +**Example:** + +```javascript +const database = await sqlClient.getDatabase('my-db'); +if (database) { + console.log(`Retrieved database: ${database.name}`); +} else { + console.error('Database not found'); +} +``` + ### `getDatabases` Retrieves a list of databases with optional filtering and pagination. @@ -382,17 +370,24 @@ Retrieves a list of databases with optional filtering and pagination. **Parameters:** - `params?: AzionDatabaseCollectionOptions` - Optional parameters for filtering and pagination. - - `ordering?: string` - Field to order the results by. - - `page?: number` - Page number for pagination. - - `page_size?: number` - Number of items per page. - - `search?: string` - Search term to filter databases. -- `options?: OptionsParams` - Object options params. - - `debug?: boolean` - Enable debug mode for detailed logging. +- `options?: AzionClientOptions` - Optional parameters for the retrieval. **Returns:** - `Promise` - Array of database objects or null if retrieval failed. +**Example:** + +```javascript +const databases = await sqlClient.getDatabases({ page: 1, page_size: 10, search: 'test' }); +if (databases) { + console.log(`Retrieved ${databases.length} databases`); + databases.forEach((db) => console.log(`- ${db.name} (ID: ${db.id})`)); +} else { + console.error('Failed to retrieve databases'); +} +``` + ### `useQuery` Executes a query on a specific database. @@ -401,12 +396,47 @@ Executes a query on a specific database. - `name: string` - Name of the database to query. - `statements: string[]` - Array of SQL statements to execute. -- `options?: OptionsParams` - Object options params. - - `debug?: boolean` - Enable debug mode for detailed logging. +- `options?: AzionClientOptions` - Optional parameters for the query. + +**Returns:** + +- `Promise` - Query result object or null if execution failed. + +**Example:** + +```javascript +const result = await sqlClient.useQuery('my-db', ['SELECT * FROM users']); +if (result) { + console.log(`Query executed. Rows returned: ${result.rows.length}`); +} else { + console.error('Query execution failed'); +} +``` + +### `useExecute` + +Executes a set of SQL statements on a specific database. + +**Parameters:** + +- `name: string` - Name of the database to execute the statements on. +- `statements: string[]` - Array of SQL statements to execute. +- `options?: AzionClientOptions` - Optional parameters for the execution. **Returns:** -- `Promise` - Query result object or null if execution failed. +- `Promise` - Execution result object or null if execution failed. + +**Example:** + +```javascript +const result = await sqlClient.useExecute('my-db', ['INSERT INTO users (name) VALUES ("John")']); +if (result?.state === 'executed') { + console.log('Executed with success'); +} else { + console.error('Execution failed'); +} +``` ### `createClient` @@ -414,7 +444,7 @@ Creates an SQL client with methods to interact with Azion Edge SQL databases. **Parameters:** -- `config?: Partial<{ token: string; options?: OptionsParams }>` - Configuration options for the SQL client. +- `config?: Partial<{ token: string; options?: AzionClientOptions }>` - Configuration options for the SQL client. **Returns:** @@ -422,22 +452,25 @@ Creates an SQL client with methods to interact with Azion Edge SQL databases. ## Types +## Types + ### `ClientConfig` Configuration options for the SQL client. - `token?: string` - Your Azion API token. -- `options?: OptionsParams` - Object options params. - - `debug?: boolean` - Enable debug mode for detailed logging. +- `options?: AzionClientOptions` - Optional parameters for the client configuration. ### `AzionSQLClient` An object with methods to interact with SQL databases. - `createDatabase: (name: string) => Promise` -- `deleteDatabase: (id: number) => Promise` +- `deleteDatabase: (id: number) => Promise` - `getDatabase: (name: string) => Promise` -- `getDatabases: (params?: DatabaseCollectionOptions) => Promise` +- `getDatabases: (params?: AzionDatabaseCollectionOptions) => Promise` +- `useQuery: (name: string, statements: string[], options?: AzionClientOptions) => Promise` +- `useExecute: (name: string, statements: string[], options?: AzionClientOptions) => Promise` ### `AzionDatabase` @@ -450,9 +483,9 @@ The database object. - `created_at: string` - `updated_at: string` - `deleted_at: string | null` -- `query: (statements: string[], options?: OptionsParams) => Promise` -- `execute: (statements: string[], options?: OptionsParams) => Promise` -- `listTables: (options?: OptionsParams) => Promise` +- `query: (statements: string[], options?: AzionClientOptions) => Promise` +- `execute: (statements: string[], options?: AzionClientOptions) => Promise` +- `listTables: (options?: AzionClientOptions) => Promise` ### `DeletedAzionDatabase` @@ -469,6 +502,57 @@ The response object from a delete database request. - `statement: string` - `rows: (number | string)[][]` +### `AzionClientOptions` + +Optional parameters for the client configuration. + +- `debug?: boolean` +- `force?: boolean` + +### `AzionDatabaseCollectionOptions` + +Optional parameters for filtering and pagination. + +- `ordering?: string` +- `page?: number` +- `page_size?: number` +- `search?: string` + +### `AzionQueryResponse` + +The response object from a query execution. + +- `state: 'executed' | 'pending' | 'executed-runtime'` +- `data: QueryResult[] | NonSelectQueryResult` +- `toObject?: () => JsonObjectQueryExecutionResponse` + +### `NonSelectQueryResult` + +The result object for non-select queries. + +- `info?: AzionQueryExecutionInfo` + +### `AzionQueryExecutionInfo` + +Information about the query execution. + +- `rowsRead?: number` +- `rowsWritten?: number` +- `durationMs?: number` + +### `AzionQueryExecutionParams` + +Parameters for query execution. + +- `statements: string[]` +- `params?: (AzionQueryParams | Record)[]` + +### `AzionQueryParams` + +Query parameters. + +- `string | number | boolean | null` + ## Contributing Feel free to submit issues or pull requests to improve the functionality or documentation. diff --git a/packages/sql/src/services/api/index.ts b/packages/sql/src/services/api/index.ts index 3073781..8f1c612 100644 --- a/packages/sql/src/services/api/index.ts +++ b/packages/sql/src/services/api/index.ts @@ -9,6 +9,13 @@ import type { const BASE_URL = 'https://api.azion.com/v4/edge_sql/databases'; +/** + * Creates a new Edge Database. + * @param {string} token - The authorization token. + * @param {string} name - The name of the database. + * @param {boolean} [debug] - Optional debug flag. + * @returns {Promise} The response from the API or null if an error occurs. + */ const postEdgeDatabase = async ( token: string, name: string, @@ -32,6 +39,13 @@ const postEdgeDatabase = async ( } }; +/** + * Deletes an existing Edge Database. + * @param {string} token - The authorization token. + * @param {number} id - The ID of the database to delete. + * @param {boolean} [debug] - Optional debug flag. + * @returns {Promise} The response from the API or null if an error occurs. + */ const deleteEdgeDatabase = async ( token: string, id: number, @@ -52,6 +66,14 @@ const deleteEdgeDatabase = async ( } }; +/** + * Executes a query on an Edge Database. + * @param {string} token - The authorization token. + * @param {number} id - The ID of the database to query. + * @param {string[]} statements - The SQL statements to execute. + * @param {boolean} [debug] - Optional debug flag. + * @returns {Promise} The response from the API or null if an error occurs. + */ const postQueryEdgeDatabase = async ( token: string, id: number, @@ -103,6 +125,13 @@ const postQueryEdgeDatabase = async ( } }; +/** + * Retrieves an Edge Database by ID. + * @param {string} token - The authorization token. + * @param {number} id - The ID of the database to retrieve. + * @param {boolean} [debug] - Optional debug flag. + * @returns {Promise} The response from the API or null if an error occurs. + */ const getEdgeDatabaseById = async ( token: string, id: number, @@ -124,6 +153,13 @@ const getEdgeDatabaseById = async ( } }; +/** + * Retrieves a list of Edge Databases. + * @param {string} token - The authorization token. + * @param {Partial} [params] - Optional query parameters. + * @param {boolean} [debug] - Optional debug flag. + * @returns {Promise} The response from the API or null if an error occurs. + */ const getEdgeDatabases = async ( token: string, params?: Partial, diff --git a/packages/sql/src/services/runtime/index.ts b/packages/sql/src/services/runtime/index.ts index af2f4b7..0daeed1 100644 --- a/packages/sql/src/services/runtime/index.ts +++ b/packages/sql/src/services/runtime/index.ts @@ -10,10 +10,17 @@ export class InternalAzionSql { private database: Azion.Sql.Database | null = null; constructor() { - this.database = getAzionSql().Database; + this.database = getAzionSql()?.Database || null; } - mapperQuery = async (resultRows: { statement: string; result: Azion.Sql.Rows }[]) => { + /** + * Maps query results to a structured format. + * @param {Array<{ statement: string; result: Azion.Sql.Rows }>} resultRows - The result rows from the query. + * @returns {Promise>} The mapped query results. + */ + mapperQuery = async ( + resultRows: { statement: string; result: Azion.Sql.Rows }[], + ): Promise<(QueryResult | NonSelectQueryResult)[]> => { const resultStatements: QueryResult[] | NonSelectQueryResult[] = []; for (const row of resultRows) { const columns = row.result.columnCount(); @@ -43,6 +50,13 @@ export class InternalAzionSql { return Promise.resolve(resultStatements); }; + /** + * Executes a series of SQL statements on the specified database. + * @param {string} name - The name of the database. + * @param {string[]} statements - The SQL statements to execute. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} The results of the executed statements. + */ query = async ( name: string, statements: string[], From 1d868d7fe6a6905bf915f6a29d037f5c364d2948 Mon Sep 17 00:00:00 2001 From: Magnun A V F <103958633+MagnunAVFAzion@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:45:15 -0300 Subject: [PATCH 05/45] docs: update jwt docs (#29) --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/README.md b/README.md index 692b791..24c7442 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ These libraries are designed to be versatile and can be used both within and out - [Purge](#purge) - [Utilities](#utilities) - [Cookies](#cookies) + - [Jwt](#jwt) - [WASM Image Processor](#wasm-image-processor) - [Utils](#utils) - [Types](#types) @@ -260,6 +261,60 @@ setCookie(response, 'my-cookie', 'cookie-value', options); Read more in the [Cookies README](./packages/cookies/README.md). +### Jwt + +The Jwt library provides methods to sign, verify and decode tokens. + +#### Examples + +**JavaScript:** + +```javascript +import { sign, verify, decode } from "azion/jwt"; + +const key = 'your-key'; + +// sign +const inputPayload = { userId: 123, exp: Math.floor(Date.now() / 1000) + 3600 }; // 1 hour expiration +const token = await sign(inputPayload, key); +console.log(`created token: ${token}`); + +// verify +const verifyResult = await verify(token, key); +console.log(`verify result: ${JSON.stringify(verifyResult)}`); + +// decode +const { header, payload } = decode(token); +console.log(`decode result: ${JSON.stringify({header, payload})}`); +``` + +**TypeScript:** + +```typescript +import { sign, verify, decode } from "azion/jwt"; +import type { JWTPayload } from 'azion/jwt'; + +const key: string = 'your-key'; + +// sign +const inputPayload: JWTPayload = { userId: 123, exp: Math.floor(Date.now() / 1000) + 3600 }; // 1 hour expiration +const token: string = await sign(inputPayload, key); +console.log(`created token: ${token}`); + +// verify +const verifyResult: JWTPayload = await verify(token, key); +console.log(`verify result: ${JSON.stringify(verifyResult)}`); + +// decode +const { header, payload }: { + header: any; + payload: JWTPayload; +} = decode(token); +console.log(`decode result: ${JSON.stringify({header, payload})}`); +``` + +Read more in the [Jwt README](./packages/jwt/README.md). + ### WASM Image Processor The WASM Image Processor library provides methods to process images using WebAssembly. From 5cf75c8bca9ac4adab95377324864e1cade546d5 Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Thu, 29 Aug 2024 15:52:05 -0300 Subject: [PATCH 06/45] docs: jwt docs --- packages/jwt/README.md | 9 ++--- packages/jwt/src/jwt/index.ts | 71 +++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/packages/jwt/README.md b/packages/jwt/README.md index 8b6b3df..477e11f 100644 --- a/packages/jwt/README.md +++ b/packages/jwt/README.md @@ -57,8 +57,7 @@ sign(payload, privateKey).then((token) => console.log(token)); // Outputs the si **TypeScript:** ```typescript -import { sign } from 'azion/jwt'; -import type { JWTPayload } from 'azion/jwt/types'; +import { sign, JWTPayload } from 'azion/jwt'; const privateKey: string = 'your-private-key'; const payload: JWTPayload = { userId: 123, exp: Math.floor(Date.now() / 1000) + 3600 }; // 1 hour expiration @@ -82,8 +81,7 @@ verify(token, publicKey) **TypeScript:** ```typescript -import { verify } from 'azion/jwt'; -import type { JWTPayload } from 'azion/jwt/types'; +import { verify, JWTPayload } from 'azion/jwt'; const publicKey: string = 'your-public-key'; const token: string = 'your-jwt-token'; @@ -107,8 +105,7 @@ console.log(header, payload); // Outputs the decoded header and payload **TypeScript:** ```typescript -import { decode } from 'azion/jwt'; -import type { TokenHeader, JWTPayload } from 'azion/jwt/types'; +import { decode, TokenHeader, JWTPayload } from 'azion/jwt'; const token: string = 'your-jwt-token'; const { header, payload }: { header: TokenHeader; payload: JWTPayload } = decode(token); diff --git a/packages/jwt/src/jwt/index.ts b/packages/jwt/src/jwt/index.ts index afa0b1e..d39d879 100644 --- a/packages/jwt/src/jwt/index.ts +++ b/packages/jwt/src/jwt/index.ts @@ -20,18 +20,54 @@ import { signing, verifying } from '../jws'; import { decodeBase64Url, encodeBase64Url } from '../utils/encode'; import { utf8Decoder, utf8Encoder } from '../utils/utf8'; +/** + * Encodes a JWT part (header or payload) to a Base64 URL string. + * @param part - The part to encode. + * @returns The Base64 URL encoded string. + * @example + * const encodedHeader = encodeJwtPart({ alg: 'HS256', typ: 'JWT' }); + * console.log(encodedHeader); + */ const encodeJwtPart = (part: unknown): string => encodeBase64Url(utf8Encoder.encode(JSON.stringify(part))).replace(/=/g, ''); + +/** + * Encodes a signature part to a Base64 URL string. + * @param buf - The buffer to encode. + * @returns The Base64 URL encoded string. + * @example + * const signature = encodeSignaturePart(new Uint8Array([1, 2, 3])); + * console.log(signature); + */ const encodeSignaturePart = (buf: ArrayBufferLike): string => encodeBase64Url(buf).replace(/=/g, ''); +/** + * Decodes a JWT part (header or payload) from a Base64 URL string. + * @param part - The part to decode. + * @returns The decoded TokenHeader or JWTPayload, or undefined if decoding fails. + * @example + * const decodedHeader = decodeJwtPart(encodedHeader); + * console.log(decodedHeader); + */ const decodeJwtPart = (part: string): TokenHeader | JWTPayload | undefined => JSON.parse(utf8Decoder.decode(decodeBase64Url(part))); +/** + * Interface representing the JWT header. + */ export interface TokenHeader { alg: SignatureAlgorithm; typ?: 'JWT'; } +/** + * Type guard to check if an object is a TokenHeader. + * @param obj - The object to check. + * @returns True if the object is a TokenHeader, false otherwise. + * @example + * const isHeader = isTokenHeader({ alg: 'HS256', typ: 'JWT' }); + * console.log(isHeader); // true + */ export function isTokenHeader(obj: unknown): obj is TokenHeader { if (typeof obj === 'object' && obj !== null) { const objWithAlg = obj as { [key: string]: unknown }; @@ -44,6 +80,16 @@ export function isTokenHeader(obj: unknown): obj is TokenHeader { return false; } +/** + * Signs a JWT payload with a given private key and algorithm. + * @param payload - The JWT payload to sign. + * @param privateKey - The private key to sign the payload with. + * @param alg - The signature algorithm to use (default is 'HS256'). + * @returns The signed JWT as a string. + * @example + * const token = await sign({ sub: '1234567890', name: 'John Doe', iat: 1516239022 }, privateKey); + * console.log(token); + */ export const sign = async ( payload: JWTPayload, privateKey: SignatureKey, @@ -60,6 +106,22 @@ export const sign = async ( return `${partialToken}.${signature}`; }; +/** + * Verifies a JWT with a given public key and algorithm. + * @param token - The JWT to verify. + * @param publicKey - The public key to verify the JWT with. + * @param alg - The signature algorithm to use (default is 'HS256'). + * @returns The decoded JWT payload if verification is successful. + * @throws {JwtTokenInvalid} If the token is invalid. + * @throws {JwtHeaderInvalid} If the token header is invalid. + * @throws {JwtTokenNotBefore} If the token is not yet valid. + * @throws {JwtTokenExpired} If the token has expired. + * @throws {JwtTokenIssuedAt} If the token was issued in the future. + * @throws {JwtTokenSignatureMismatched} If the token signature does not match. + * @example + * const payload = await verify(token, publicKey); + * console.log(payload); + */ export const verify = async ( token: string, publicKey: SignatureKey, @@ -94,6 +156,15 @@ export const verify = async ( return payload; }; +/** + * Decodes a JWT into its header and payload. + * @param token - The JWT to decode. + * @returns An object containing the decoded header and payload. + * @throws {JwtTokenInvalid} If the token is invalid. + * @example + * const { header, payload } = decode(token); + * console.log(header, payload); + */ export const decode = (token: string): { header: TokenHeader; payload: JWTPayload } => { try { const [h, p] = token.split('.'); From 72a06ac1295eb5a0b73c9608fb3576583043214a Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Thu, 29 Aug 2024 15:55:01 -0300 Subject: [PATCH 07/45] docs: wasm docs --- packages/wasm-image-processor/README.MD | 15 +++++---------- packages/wasm-image-processor/src/types.ts | 4 +++- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/wasm-image-processor/README.MD b/packages/wasm-image-processor/README.MD index 40d7712..aa539f1 100644 --- a/packages/wasm-image-processor/README.MD +++ b/packages/wasm-image-processor/README.MD @@ -50,8 +50,7 @@ const image = await loadImage('https://example.com/image.jpg'); **TypeScript:** ```typescript -import { loadImage } from 'azion/wasm-image-processor'; -import { WasmImage } from 'azion/wasm-image-processor/types'; +import { loadImage, WasmImage } from 'azion/wasm-image-processor'; const image: WasmImage = await loadImage('https://example.com/image.jpg'); ``` @@ -70,8 +69,7 @@ const resizedImage = image.resize(0.5, 0.5); **TypeScript:** ```typescript -import { loadImage } from 'azion/wasm-image-processor'; -import { WasmImage } from 'azion/wasm-image-processor/types'; +import { loadImage, WasmImage } from 'azion/wasm-image-processor'; const image: WasmImage = await loadImage('https://example.com/image.jpg'); const resizedImage: WasmImage = image.resize(0.5, 0.5); @@ -92,8 +90,7 @@ console.log(imageResponse); **TypeScript:** ```typescript -import { loadImage } from 'azion/wasm-image-processor'; -import { WasmImage, SupportedImageFormat } from 'azion/wasm-image-processor/types'; +import { loadImage, WasmImage, SupportedImageFormat } from 'azion/wasm-image-processor'; const image: WasmImage = await loadImage('https://example.com/image.jpg'); const imageResponse: Response = image.getImageResponse('jpeg' as SupportedImageFormat); @@ -114,8 +111,7 @@ image.clean(); **TypeScript:** ```typescript -import { loadImage } from 'azion/wasm-image-processor'; -import { WasmImage } from 'azion/wasm-image-processor/types'; +import { loadImage, WasmImage } from 'azion/wasm-image-processor'; const image: WasmImage = await loadImage('https://example.com/image.jpg'); image.clean(); @@ -138,8 +134,7 @@ clean(resizedImage); **TypeScript:** ```typescript -import { loadImage, resize, getImageResponse, clean } from 'azion/wasm-image-processor'; -import { PhotonImage } from 'azion/wasm-image-processor/types'; +import { loadImage, resize, getImageResponse, clean, PhotonImage } from 'azion/wasm-image-processor'; const image: WasmImage = await loadImage('https://example.com/image.jpg'); const resizedImage: PhotonImage = resize(image.image, 0.5, 0.5); diff --git a/packages/wasm-image-processor/src/types.ts b/packages/wasm-image-processor/src/types.ts index c455c60..40f5d77 100644 --- a/packages/wasm-image-processor/src/types.ts +++ b/packages/wasm-image-processor/src/types.ts @@ -1,4 +1,4 @@ -import { PhotonImage } from './photon/lib/index'; +import { PhotonImage as PhotonImageType } from './photon/lib/index'; export interface WasmImage { /** The underlying PhotonImage object. */ @@ -26,3 +26,5 @@ export interface WasmImage { } export type SupportedImageFormat = 'webp' | 'jpeg' | 'png'; + +export type PhotonImage = PhotonImageType; From a4a2bd39a2e01184aaf0bbf320c194d2364853e6 Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Thu, 29 Aug 2024 16:19:51 -0300 Subject: [PATCH 08/45] feat: jsdoc for azion client --- packages/client/src/index.ts | 92 ++++++++++---------------- packages/client/src/types.ts | 117 ++++++++++++++++++++++++++++++++++ packages/storage/src/index.ts | 2 + 3 files changed, 151 insertions(+), 60 deletions(-) diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 7ef649e..a3382e7 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,16 +1,12 @@ import { defineConfig } from 'azion/config'; -import createPurgeClient from 'azion/purge'; -import createSqlClient from 'azion/sql'; -import createStorageClient from 'azion/storage'; - -import { AzionPurgeClient } from '../../purge/src/types'; -import { AzionSQLClient } from '../../sql/src/types'; -import { AzionStorageClient } from '../../storage/src/types'; +import createPurgeClient, { AzionPurgeClient } from 'azion/purge'; +import createSqlClient, { AzionSQLClient } from 'azion/sql'; +import createStorageClient, { AzionStorageClient } from 'azion/storage'; import { AzionClient, AzionClientConfig } from './types'; /** - * Creates an Azion Client with methods to interact with Azion + * Creates an Azion Client with methods to interact with Azion services * * @param {AzionClientConfig} [config] - Client configuration options. * @param {string} [config.token] - Authentication token for Azion API. @@ -24,71 +20,47 @@ import { AzionClient, AzionClientConfig } from './types'; * @example * // Create a client using environment variables for token * const client = createClient({ debug: true }); - * - * @example - * // Use the SQL client - * const databases = await client.sql.getDatabases(); - * - * @example - * // Use the Storage client - * const buckets = await client.storage.getBuckets({ params: { page: 1, page_size: 10 } }); - * - * @example - * // Use the Purge client - * const purgeResult = await client.purge.purgeURL('http://example.com/image.jpg'); */ function createClient({ token, options }: AzionClientConfig = {}): AzionClient { + /** + * Storage client with methods to interact with Azion Edge Storage. + * @type {AzionStorageClient} + */ const storageClient: AzionStorageClient = createStorageClient({ token, options }); + + /** + * SQL client with methods to interact with Azion Edge SQL databases. + * @type {AzionSQLClient} + */ const sqlClient: AzionSQLClient = createSqlClient({ token, options }); + + /** + * Purge client with methods to interact with Azion Edge Purge. + * @type {AzionPurgeClient} + */ const purgeClient: AzionPurgeClient = createPurgeClient({ token, options }); - return { - /** - * Storage client with methods to interact with Azion Edge Storage. - * - * @example - * // Create a new bucket - * const newBucket = await client.storage.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); - * - * // Get all buckets - * const allBuckets = await client.storage.getBuckets({ params: { page: 1, page_size: 10 } }); - * - * // Delete a bucket - * const deletedBucket = await client.storage.deleteBucket({ name: 'my-bucket' }); - */ + /** + * Azion Client object containing Storage, SQL, and Purge clients. + * Use this object to interact with various Azion services. + * + * @type {AzionClient} + * + * @property {AzionStorageClient} storage - Client for Azion Edge Storage operations. + * @property {AzionSQLClient} sql - Client for Azion Edge SQL database operations. + * @property {AzionPurgeClient} purge - Client for Azion Edge Purge operations. + */ + const client: AzionClient = { storage: storageClient, - /** - * SQL client with methods to interact with Azion Edge SQL databases. - * - * @example - * // Create a new database - * const newDatabase = await client.sql.createDatabase('my-new-db'); - * - * // Get all databases - * const allDatabases = await client.sql.getDatabases(); - * - * // Query a database - * const queryResult = await client.sql.query('SELECT * FROM users'); - */ sql: sqlClient, - /** - * Purge client with methods to interact with Azion Edge Purge. - * - * @example - * // Purge a URL - * const purgeResult = await client.purge.purgeURL(['http://example.com/image.jpg']); - * - * // Purge a cache key - * const cacheKeyResult = await client.purge.purgeCacheKey(['my-cache-key-1', 'my-cache-key-2']); - * - * // Purge using a wildcard - * const wildcardResult = await client.purge.purgeWildCard(['http://example.com/*']); - */ purge: purgeClient, }; + + return client; } export { createClient, defineConfig }; + export default createClient; export type * from './types'; diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index 09d0cf9..62161ce 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -5,11 +5,128 @@ import { AzionClientOptions, AzionSQLClient } from '../../sql/src/types'; import { AzionStorageClient } from '../../storage/src/types'; export interface AzionClient { + /** + * Storage client with methods to interact with Azion Edge Storage. + * + * @type {AzionStorageClient} + * + * @example + * // Create a new bucket + * const newBucket = await client.storage.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); + * + * @example + * // Get all buckets + * const allBuckets = await client.storage.getBuckets({ params: { page: 1, page_size: 10 } }); + * + * @example + * // Get a specific bucket and perform operations + * const bucket = await client.storage.getBucket({ name: 'my-bucket' }); + * if (bucket) { + * // Upload a new object + * const newObject = await bucket.createObject({ key: 'example.txt', content: 'Hello, World!' }); + * + * // Get all objects in the bucket + * const objects = await bucket.getObjects({ params: { page: 1, page_size: 10 } }); + * + * // Get a specific object + * const object = await bucket.getObjectByKey({ key: 'example.txt' }); + * + * // Update an object + * const updatedObject = await bucket.updateObject({ key: 'example.txt', content: 'Updated content' }); + * + * // Delete an object + * const deletedObject = await bucket.deleteObject({ key: 'example.txt' }); + * } + * + * @example + * // Delete a bucket + * const deletedBucket = await client.storage.deleteBucket({ name: 'my-bucket' }); + */ storage: AzionStorageClient; + + /** + * SQL client with methods to interact with Azion Edge SQL databases. + * + * @type {AzionSQLClient} + * + * @example + * // Create a new database + * const newDatabase = await client.sql.createDatabase('my-new-db'); + * + * @example + * // Get all databases + * const allDatabases = await client.sql.getDatabases(); + * + * @example + * // Get a specific database and perform operations + * const db = await client.sql.getDatabase('my-db'); + * if (db) { + * // Execute a query + * const queryResult = await db.query(['SELECT * FROM users WHERE id = ?', 1]); + * + * // Execute multiple statements + * const executeResult = await db.execute([ + * 'INSERT INTO users (name, email) VALUES (?, ?)', + * 'UPDATE users SET last_login = NOW() WHERE id = ?' + * ], ['John Doe', 'john@example.com', 1]); + * + * // List tables in the database + * const tables = await db.listTables(); + * } + * + * @example + * // Delete a database + * const deletedDatabase = await client.sql.deleteDatabase(123); // Using database ID + */ sql: AzionSQLClient; + + /** + * Purge client with methods to interact with Azion Edge Purge. + * + * @type {AzionPurgeClient} + * + * @example + * // Purge a URL + * const purgeResult = await client.purge.purgeURL(['http://example.com/image.jpg']); + * + * @example + * // Purge a cache key + * const cacheKeyResult = await client.purge.purgeCacheKey(['my-cache-key-1', 'my-cache-key-2']); + * + * @example + * // Purge using a wildcard + * const wildcardResult = await client.purge.purgeWildCard(['http://example.com/*']); + */ purge: AzionPurgeClient; } +/** + * Configuration options for creating an Azion client. + * + * @interface AzionClientConfig + * + * @property {string} [token] - The authentication token for Azion API. If not provided, + * the client will attempt to use the AZION_TOKEN environment variable. + * + * @property {AzionClientOptions} [options] - Additional options for configuring the client. + * @property {boolean} [options.debug] - Enable debug mode for detailed logging. + * + * @example + * // Create a client with a token and debug mode enabled + * const config: AzionClientConfig = { + * token: 'your-api-token', + * options: { debug: true } + * }; + * const client = createClient(config); + * + * @example + * // Create a client using environment variables for token and default options + * const client = createClient(); + */ +export interface AzionClientConfig { + token?: string; + options?: AzionClientOptions; +} export interface AzionClientConfig { token?: string; options?: AzionClientOptions; diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts index 5f18aab..6546750 100644 --- a/packages/storage/src/index.ts +++ b/packages/storage/src/index.ts @@ -750,3 +750,5 @@ export { }; export default client; + +export type * from './types'; From b2700122fac4abe1a68dbe8d51da2ca32fc47b4a Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Thu, 29 Aug 2024 16:36:06 -0300 Subject: [PATCH 09/45] refactor: expose client jsdoc --- packages/purge/src/index.ts | 48 +---------- packages/purge/src/types.ts | 78 ++++++++++++++++++ packages/sql/src/index.ts | 64 --------------- packages/sql/src/types.ts | 147 +++++++++++++++++++++++++++++++-- packages/storage/src/index.ts | 36 -------- packages/storage/src/types.ts | 150 +++++++++++++++++++++++++++++++++- 6 files changed, 368 insertions(+), 155 deletions(-) diff --git a/packages/purge/src/index.ts b/packages/purge/src/index.ts index 152c8e2..b20b2e8 100644 --- a/packages/purge/src/index.ts +++ b/packages/purge/src/index.ts @@ -119,57 +119,10 @@ const client: CreateAzionPurgeClient = ( const tokenValue = resolveToken(config?.token); const client: AzionPurgeClient = { - /** - * Purge a URL from the Azion Edge cache. - * - * @param {string[]} url - URLs to purge. - * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The purge response or null if the purge failed. - * - * @example - * const response = await purgeClient.purgeURL(['http://www.domain.com/path/image.jpg'], { debug: true }); - * if (response) { - * console.log('Purge successful:', response); - * } else { - * console.error('Purge failed'); - * } - */ purgeURL: (url: string[], options?: AzionClientOptions): Promise => purgeURLMethod(tokenValue, url, options), - - /** - * Purge a Cache Key from the Azion Edge cache. - * - * @param {string[]} cacheKey - Cache Keys to purge. - * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The purge response or null if the purge failed. - * - * @example - * const response = await purgeClient.purgeCacheKey(['http://www.domain.com/path/image.jpg'], { debug: true }); - * if (response) { - * console.log('Purge successful:', response); - * } else { - * console.error('Purge failed'); - * } - */ purgeCacheKey: (cacheKey: string[], options?: AzionClientOptions): Promise => purgeCacheKeyMethod(tokenValue, cacheKey, options), - - /** - * Purge using a wildcard expression from the Azion Edge cache. - * - * @param {string[]} wildcard - Wildcard expressions to purge. - * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The purge response or null if the purge failed. - * - * @example - * const response = await purgeClient.purgeWildCard(['http://www.domain.com/path/image.jpg*'], { debug: true }); - * if (response) { - * console.log('Purge successful:', response); - * } else { - * console.error('Purge failed'); - * } - */ purgeWildCard: (wildcard: string[], options?: AzionClientOptions): Promise => purgeWildCardMethod(tokenValue, wildcard, options), }; @@ -183,6 +136,7 @@ export { purgeURLWrapper as purgeURL, purgeWildCardWrapper as purgeWildCard, }; + export default client; export type * from './types'; diff --git a/packages/purge/src/types.ts b/packages/purge/src/types.ts index 289be01..e364b8d 100644 --- a/packages/purge/src/types.ts +++ b/packages/purge/src/types.ts @@ -11,15 +11,93 @@ export interface AzionPurge { } export interface AzionPurgeClient { + /** + * Purge a URL from the Azion Edge cache. + * + * @param {string[]} url - URLs to purge. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} The purge response or null if the purge failed. + * + * @example + * const response = await purgeClient.purgeURL(['http://www.domain.com/path/image.jpg'], { debug: true }); + * if (response) { + * console.log('Purge successful:', response); + * } else { + * console.error('Purge failed'); + * } + */ purgeURL: (urls: string[]) => Promise; + /** + * Purge a Cache Key from the Azion Edge cache. + * + * @param {string[]} cacheKey - Cache Keys to purge. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} The purge response or null if the purge failed. + * + * @example + * const response = await purgeClient.purgeCacheKey(['http://www.domain.com/path/image.jpg'], { debug: true }); + * if (response) { + * console.log('Purge successful:', response); + * } else { + * console.error('Purge failed'); + * } + */ purgeCacheKey: (cacheKeys: string[]) => Promise; + /** + * Purge using a wildcard expression from the Azion Edge cache. + * + * @param {string[]} wildcard - Wildcard expressions to purge. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise} The purge response or null if the purge failed. + * + * @example + * const response = await purgeClient.purgeWildCard(['http://www.domain.com/path/image.jpg*'], { debug: true }); + * if (response) { + * console.log('Purge successful:', response); + * } else { + * console.error('Purge failed'); + * } + */ purgeWildCard: (wildcards: string[]) => Promise; } +/** + * Function type for creating an Azion Purge Client. + * + * @param {Object} [config] - Configuration options for the Purge client. + * @param {string} [config.token] - Authentication token for Azion API. If not provided, + * the client will attempt to use the AZION_TOKEN environment variable. + * @param {AzionClientOptions} [config.options] - Additional client options. + * + * @returns {AzionPurgeClient} An instance of the Azion Purge Client. + * + * @example + * // Create a Purge client with a token and debug mode enabled + * const purgeClient = createAzionPurgeClient({ + * token: 'your-api-token', + * options: { debug: true } + * }); + * + * @example + * // Create a Purge client using environment variables for token + * const purgeClient = createAzionPurgeClient(); + */ export type CreateAzionPurgeClient = ( config?: Partial<{ token: string; options?: AzionClientOptions }>, ) => AzionPurgeClient; +/** + * Options for configuring the Azion client behavior. + * + * @property {boolean} [debug] - Enable debug mode for detailed logging. + * @property {boolean} [force] - Force the operation even if it might be destructive. + * + * @example + * const options: AzionClientOptions = { + * debug: true, + * force: false + * }; + */ export type AzionClientOptions = { debug?: boolean; force?: boolean; diff --git a/packages/sql/src/index.ts b/packages/sql/src/index.ts index 63f8f95..2487dc7 100644 --- a/packages/sql/src/index.ts +++ b/packages/sql/src/index.ts @@ -397,76 +397,12 @@ const client: CreateAzionSQLClient = ( const debugValue = resolveDebug(config?.options?.debug); const client: AzionSQLClient = { - /** - * Creates a new database. - * - * @param {string} name - Name of the new database. - * @returns {Promise} The created database object or null if creation failed. - * - * @example - * const newDatabase = await sqlClient.createDatabase('my-new-db'); - * if (newDatabase) { - * console.log(`Database created with ID: ${newDatabase.id}`); - * } else { - * console.error('Failed to create database'); - * } - */ createDatabase: (name: string): Promise => createDatabaseMethod(tokenValue, name, { ...config, debug: debugValue }), - - /** - * Deletes a database by its ID. - * - * @param {number} id - ID of the database to delete. - * @returns {Promise} Object confirming deletion or null if deletion failed. - * - * @example - * const result = await sqlClient.deleteDatabase(123); - * if (result) { - * console.log(`Database ${result.id} deleted successfully`); - * } else { - * console.error('Failed to delete database'); - * } - */ deleteDatabase: (id: number): Promise => deleteDatabaseMethod(tokenValue, id, { ...config, debug: debugValue }), - - /** - * Retrieves a database by its Name. - * - * @param {string} name - Name of the database to retrieve. - * @returns {Promise} The retrieved database object or null if not found. - * - * @example - * const database = await sqlClient.getDatabase('my-db'); - * if (database) { - * console.log(`Retrieved database: ${database.name}`); - * } else { - * console.error('Database not found'); - * } - */ getDatabase: (name: string): Promise => getDatabaseMethod(tokenValue, name, { ...config, debug: debugValue }), - - /** - * Retrieves a list of databases with optional filtering and pagination. - * - * @param {AzionDatabaseCollectionOptions} [params] - Optional parameters for filtering and pagination. - * @param {string} [params.ordering] - Field to order the results by. - * @param {number} [params.page] - Page number for pagination. - * @param {number} [params.page_size] - Number of items per page. - * @param {string} [params.search] - Search term to filter databases. - * @returns {Promise} Array of database objects or null if retrieval failed. - * - * @example - * const databases = await sqlClient.getDatabases({ page: 1, page_size: 10, search: 'test' }); - * if (databases) { - * console.log(`Retrieved ${databases.length} databases`); - * databases.forEach(db => console.log(`- ${db.name} (ID: ${db.id})`)); - * } else { - * console.error('Failed to retrieve databases'); - * } - */ getDatabases: (params?: AzionDatabaseCollectionOptions): Promise => getDatabasesMethod(tokenValue, params, { ...config, debug: debugValue }), } as const; diff --git a/packages/sql/src/types.ts b/packages/sql/src/types.ts index 1f43339..0695fc2 100644 --- a/packages/sql/src/types.ts +++ b/packages/sql/src/types.ts @@ -9,8 +9,45 @@ export interface AzionDatabase { created_at: string; updated_at: string; deleted_at: string | null; + /** + * Executes a query or multiple queries on the database. + * + * @param {string[]} statements - An array of SQL statements to execute. + * @param {AzionClientOptions} [options] - Additional options for the query execution. + * @returns {Promise} A promise that resolves to the query response or null if the execution failed. + * + * @example + * const result = await database.query([ + * 'SELECT * FROM users WHERE id = ?', + * 'UPDATE users SET last_login = NOW() WHERE id = ?' + * ], { debug: true }); + */ query?: (statements: string[], options?: AzionClientOptions) => Promise; + + /** + * Executes one or more SQL statements on the database. + * + * @param {string[]} statements - An array of SQL statements to execute. + * @param {AzionClientOptions} [options] - Additional options for the execution. + * @returns {Promise} A promise that resolves to the execution response or null if the execution failed. + * + * @example + * const result = await database.execute([ + * 'INSERT INTO users (name, email) VALUES (?, ?)', + * 'DELETE FROM old_users WHERE last_login < ?' + * ], { force: true }); + */ execute?: (statements: string[], options?: AzionClientOptions) => Promise; + + /** + * Retrieves a list of tables in the database. + * + * @param {AzionClientOptions} [options] - Additional options for listing tables. + * @returns {Promise} A promise that resolves to the list of tables or null if the operation failed. + * + * @example + * const tables = await database.listTables({ debug: true }); + */ listTables?: (options?: AzionClientOptions) => Promise; } @@ -50,10 +87,79 @@ export type AzionQueryResponse = { toObject?: () => JsonObjectQueryExecutionResponse; }; +export type AzionDatabaseCollectionOptions = { + ordering?: string; + page?: number; + page_size?: number; + search?: string; +}; + export interface AzionSQLClient { + /** + * Deletes a database by its ID. + * + * @param {number} id - ID of the database to delete. + * @returns {Promise} Object confirming deletion or null if deletion failed. + * + * @example + * const result = await sqlClient.deleteDatabase(123); + * if (result) { + * console.log(`Database ${result.id} deleted successfully`); + * } else { + * console.error('Failed to delete database'); + * } + */ createDatabase: (name: string) => Promise; + /** + * Deletes a database by its ID. + * + * @param {number} id - ID of the database to delete. + * @returns {Promise} Object confirming deletion or null if deletion failed. + * + * @example + * const result = await sqlClient.deleteDatabase(123); + * if (result) { + * console.log(`Database ${result.id} deleted successfully`); + * } else { + * console.error('Failed to delete database'); + * } + */ deleteDatabase: (id: number) => Promise; + /** + * Retrieves a database by its Name. + * + * @param {string} name - Name of the database to retrieve. + * @returns {Promise} The retrieved database object or null if not found. + * + * @example + * const database = await sqlClient.getDatabase('my-db'); + * if (database) { + * console.log(`Retrieved database: ${database.name}`); + * } else { + * console.error('Database not found'); + * } + */ getDatabase?: (name: string) => Promise; + + /** + * Retrieves a list of databases with optional filtering and pagination. + * + * @param {AzionDatabaseCollectionOptions} [params] - Optional parameters for filtering and pagination. + * @param {string} [params.ordering] - Field to order the results by. + * @param {number} [params.page] - Page number for pagination. + * @param {number} [params.page_size] - Number of items per page. + * @param {string} [params.search] - Search term to filter databases. + * @returns {Promise} Array of database objects or null if retrieval failed. + * + * @example + * const databases = await sqlClient.getDatabases({ page: 1, page_size: 10, search: 'test' }); + * if (databases) { + * console.log(`Retrieved ${databases.length} databases`); + * databases.forEach(db => console.log(`- ${db.name} (ID: ${db.id})`)); + * } else { + * console.error('Failed to retrieve databases'); + * } + */ getDatabases: (params?: { ordering?: string; page?: number; @@ -61,17 +167,44 @@ export interface AzionSQLClient { search?: string; }) => Promise; } + +/** + * Function type for creating an Azion SQL Client. + * + * @param {Object} [config] - Configuration options for the SQL client. + * @param {string} [config.token] - Authentication token for Azion API. If not provided, + * the client will attempt to use the AZION_TOKEN environment variable. + * @param {AzionClientOptions} [config.options] - Additional client options. + * + * @returns {AzionSQLClient} An instance of the Azion SQL Client. + * + * @example + * // Create an SQL client with a token and debug mode enabled + * const sqlClient = createAzionSQLClient({ + * token: 'your-api-token', + * options: { debug: true } + * }); + * + * @example + * // Create an SQL client using environment variables for token + * const sqlClient = createAzionSQLClient(); + */ export type CreateAzionSQLClient = ( config?: Partial<{ token?: string; options?: AzionClientOptions }>, ) => AzionSQLClient; -export type AzionDatabaseCollectionOptions = { - ordering?: string; - page?: number; - page_size?: number; - search?: string; -}; - +/** + * Options for configuring the Azion client behavior. + * + * @property {boolean} [debug] - Enable debug mode for detailed logging. + * @property {boolean} [force] - Force the operation even if it might be destructive. + * + * @example + * const options: AzionClientOptions = { + * debug: true, + * force: false + * }; + */ export type AzionClientOptions = { debug?: boolean; force?: boolean; diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts index 6546750..35a656a 100644 --- a/packages/storage/src/index.ts +++ b/packages/storage/src/index.ts @@ -684,50 +684,14 @@ const client: CreateAzionStorageClient = ( const debugValue = resolveDebug(config?.options?.debug); const client: AzionStorageClient = { - /** - * Retrieves a list of buckets with optional filtering and pagination. - * @param {Object} params - Parameters for retrieving buckets. - * @param {AzionBucketCollectionParams} [params.params] - Optional parameters for filtering and pagination. - * @returns {Promise} Array of buckets or null if retrieval failed. - */ getBuckets: (params?: { params?: AzionBucketCollectionParams }): Promise => getBucketsMethod(tokenValue, params?.params, { ...config, debug: debugValue }), - - /** - * Creates a new bucket. - * @param {Object} params - Parameters for creating a bucket. - * @param {string} params.name - Name of the new bucket. - * @param {string} params.edge_access - Edge access configuration for the bucket. - * @returns {Promise} The created bucket or null if creation failed. - */ createBucket: ({ name, edge_access }: { name: string; edge_access: string }): Promise => createBucketMethod(tokenValue, name, edge_access, { ...config, debug: debugValue }), - - /** - * Updates an existing bucket. - * @param {Object} params - Parameters for updating a bucket. - * @param {string} params.name - Name of the bucket to update. - * @param {string} params.edge_access - New edge access configuration for the bucket. - * @returns {Promise} The updated bucket or null if update failed. - */ updateBucket: ({ name, edge_access }: { name: string; edge_access: string }): Promise => updateBucketMethod(tokenValue, name, edge_access, { ...config, debug: debugValue }), - - /** - * Deletes a bucket by its name. - * @param {Object} params - Parameters for deleting a bucket. - * @param {string} params.name - Name of the bucket to delete. - * @returns {Promise} Confirmation of deletion or null if deletion failed. - */ deleteBucket: ({ name }: { name: string }): Promise => deleteBucketMethod(tokenValue, name, { ...config, debug: debugValue }), - - /** - * Retrieves a bucket by its name. - * @param {Object} params - Parameters for retrieving a bucket. - * @param {string} params.name - Name of the bucket to retrieve. - * @returns {Promise} The retrieved bucket or null if not found. - */ getBucket: ({ name }: { name: string }): Promise => getBucketMethod(tokenValue, name, { ...config, debug: debugValue }), } as const; diff --git a/packages/storage/src/types.ts b/packages/storage/src/types.ts index 7775b30..a48cb47 100644 --- a/packages/storage/src/types.ts +++ b/packages/storage/src/types.ts @@ -1,21 +1,98 @@ // eslint-disable-next-line @typescript-eslint/no-namespace - +/** + * Represents an Azion storage bucket with methods to interact with objects. + * + * @interface AzionBucket + * + * @property {string} name - The name of the bucket. + * @property {string} edge_access - The edge access configuration for the bucket. + * @property {'executed' | 'executed-runtime' | 'pending'} [state] - The current state of the bucket. + */ export interface AzionBucket { name: string; edge_access: string; state?: 'executed' | 'executed-runtime' | 'pending'; + + /** + * Retrieves a list of objects in the bucket. + * + * @param {Object} params - Parameters for retrieving objects. + * @param {AzionObjectCollectionParams} params.params - Options for filtering and pagination. + * @returns {Promise} A promise that resolves to an array of bucket objects or null. + * + * @example + * const objects = await bucket.getObjects({ params: { max_object_count: 100 } }); + */ getObjects: (params: { params: AzionObjectCollectionParams }) => Promise; + + /** + * Retrieves a specific object from the bucket by its key. + * + * @param {Object} params - Parameters for retrieving the object. + * @param {string} params.key - The key of the object to retrieve. + * @returns {Promise} A promise that resolves to the bucket object or null if not found. + * + * @example + * const object = await bucket.getObjectByKey({ key: 'example.txt' }); + */ getObjectByKey: (params: { key: string }) => Promise; + + /** + * Creates a new object in the bucket. + * + * @param {Object} params - Parameters for creating the object. + * @param {string} params.key - The key for the new object. + * @param {string} params.content - The content of the new object. + * @param {Object} [params.options] - Additional options for the object. + * @param {string} [params.options.content_type] - The content type of the object. + * @returns {Promise} A promise that resolves to the created bucket object or null if creation failed. + * + * @example + * const newObject = await bucket.createObject({ + * key: 'new-file.txt', + * content: 'Hello, World!', + * options: { content_type: 'text/plain' } + * }); + */ createObject: (params: { key: string; content: string; options?: { content_type?: string }; }) => Promise; + + /** + * Updates an existing object in the bucket. + * + * @param {Object} params - Parameters for updating the object. + * @param {string} params.key - The key of the object to update. + * @param {string} params.content - The new content for the object. + * @param {Object} [params.options] - Additional options for the object. + * @param {string} [params.options.content_type] - The new content type for the object. + * @returns {Promise} A promise that resolves to the updated bucket object or null if update failed. + * + * @example + * const updatedObject = await bucket.updateObject({ + * key: 'existing-file.txt', + * content: 'Updated content', + * options: { content_type: 'text/plain' } + * }); + */ updateObject: (params: { key: string; content: string; options?: { content_type?: string }; }) => Promise; + + /** + * Deletes an object from the bucket. + * + * @param {Object} params - Parameters for deleting the object. + * @param {string} params.key - The key of the object to delete. + * @returns {Promise} A promise that resolves to the deleted bucket object or null if deletion failed. + * + * @example + * const deletedObject = await bucket.deleteObject({ key: 'file-to-delete.txt' }); + */ deleteObject: (params: { key: string }) => Promise; } @@ -39,10 +116,41 @@ export interface AzionDeletedBucket { } export interface AzionStorageClient { + /** + * Retrieves a list of buckets with optional filtering and pagination. + * @param {Object} params - Parameters for retrieving buckets. + * @param {AzionBucketCollectionParams} [params.params] - Optional parameters for filtering and pagination. + * @returns {Promise} Array of buckets or null if retrieval failed. + */ getBuckets: (params?: { params?: AzionBucketCollectionParams }) => Promise; + /** + * Creates a new bucket. + * @param {Object} params - Parameters for creating a bucket. + * @param {string} params.name - Name of the new bucket. + * @param {string} params.edge_access - Edge access configuration for the bucket. + * @returns {Promise} The created bucket or null if creation failed. + */ createBucket: (params: { name: string; edge_access: string }) => Promise; + /** + * Deletes a bucket by its name. + * @param {Object} params - Parameters for deleting a bucket. + * @param {string} params.name - Name of the bucket to delete. + * @returns {Promise} Confirmation of deletion or null if deletion failed. + */ updateBucket: (params: { name: string; edge_access: string }) => Promise; + /** + * Deletes a bucket by its name. + * @param {Object} params - Parameters for deleting a bucket. + * @param {string} params.name - Name of the bucket to delete. + * @returns {Promise} Confirmation of deletion or null if deletion failed. + */ deleteBucket: (params: { name: string }) => Promise; + /** + * Retrieves a bucket by its name. + * @param {Object} params - Parameters for retrieving a bucket. + * @param {string} params.name - Name of the bucket to retrieve. + * @returns {Promise} The retrieved bucket or null if not found. + */ getBucket: (params: { name: string }) => Promise; } @@ -55,11 +163,51 @@ export type AzionObjectCollectionParams = { max_object_count?: number; }; +/** + * Options for configuring the Azion client behavior. + * + * @property {boolean} [debug] - Enable debug mode for detailed logging. + * @property {boolean} [force] - Force the operation even if it might be destructive. + * + * @example + * const options: AzionClientOptions = { + * debug: true, + * force: false + * }; + */ export type AzionClientOptions = { debug?: boolean; force?: boolean; }; +/** + * Function type for creating an Azion Storage Client. + * + * @param {Object} [config] - Configuration options for the Storage client. + * @param {string} [config.token] - Authentication token for Azion API. If not provided, + * the client will attempt to use the AZION_TOKEN environment variable. + * @param {AzionClientOptions} [config.options] - Additional client options. + * + * @returns {AzionStorageClient} An instance of the Azion Storage Client. + * + * @example + * // Create a Storage client with a token and debug mode enabled + * const storageClient = createAzionStorageClient({ + * token: 'your-api-token', + * options: { debug: true } + * }); + * + * @example + * // Create a Storage client using environment variables for token + * const storageClient = createAzionStorageClient(); + * + * @example + * // Use the Storage client to create a bucket + * const newBucket = await storageClient.createBucket({ + * name: 'my-new-bucket', + * edge_access: 'public' + * }); + */ export type CreateAzionStorageClient = ( config?: Partial<{ token: string; options?: AzionClientOptions }>, ) => AzionStorageClient; From b9d1d26851781335fb40613b06fc945a033b4fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Filho?= Date: Fri, 30 Aug 2024 10:58:08 -0300 Subject: [PATCH 10/45] refactor: update the return of azion/sql functions Updating and standardizing the return of functions with the format {data, error}. --- README.md | 50 +-- packages/client/README.MD | 27 +- packages/client/src/types.ts | 6 +- packages/sql/README.MD | 221 +++++++------ packages/sql/src/index.test.ts | 81 +++-- packages/sql/src/index.ts | 344 ++++++++++++-------- packages/sql/src/services/api/index.ts | 98 ++++-- packages/sql/src/services/api/types.ts | 26 +- packages/sql/src/services/index.ts | 142 +++++--- packages/sql/src/types.ts | 107 +++--- packages/sql/src/utils/mappers/to-object.ts | 6 +- 11 files changed, 663 insertions(+), 445 deletions(-) diff --git a/README.md b/README.md index 24c7442..5b39b5d 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,10 @@ console.log(`Retrieved ${allBuckets.length} buckets`); // SQL const newDatabase = await client.sql.createDatabase('my-new-db'); -console.log(`Database created with ID: ${newDatabase.id}`); +console.log(`Database created with ID: ${newDatabase.data.id}`); const allDatabases = await client.sql.getDatabases(); -console.log(`Retrieved ${allDatabases.length} databases`); +console.log(`Retrieved ${allDatabases.data.length} databases`); // Purge const purgeResult = await client.purge.purgeURL(['http://example.com/image.jpg']); @@ -73,7 +73,8 @@ console.log(`Purge successful: ${purgeResult.items}`); ```typescript import { createClient } from 'azion'; -import { AzionClient, Bucket, AzionDatabase, Purge } from 'azion/types'; +import type { AzionClient, Bucket, Purge } from 'azion/client'; +import type { AzionDatabaseResponse, AzionDatabaseQueryResponse } from 'azion/sql'; const client: AzionClient = createClient({ token: 'your-api-token', debug: true }); @@ -85,11 +86,11 @@ const allBuckets: Bucket[] | null = await client.storage.getBuckets(); console.log(`Retrieved ${allBuckets.length} buckets`); // SQL -const newDatabase: AzionDatabase | null = await client.sql.createDatabase('my-new-db'); -console.log(`Database created with ID: ${newDatabase.id}`); +const newDatabase: AzionDatabaseResponse = await client.sql.createDatabase('my-new-db'); +console.log(`Database created with ID: ${newDatabase.data.id}`); -const allDatabases: AzionDatabase[] | null = await client.sql.getDatabases(); -console.log(`Retrieved ${allDatabases.length} databases`); +const allDatabases: AzionDatabaseResponse = await client.sql.getDatabases(); +console.log(`Retrieved ${allDatabases.data.length} databases`); // Purge const purgeResult: Purge | null = await client.purge.purgeURL(['http://example.com/image.jpg']); @@ -154,14 +155,14 @@ import { createClient } from 'azion/sql'; const client = createClient({ token: 'your-api-token', debug: true }); -const newDatabase = await client.createDatabase('my-new-db'); +const { data: newDatabase, error } = await client.createDatabase('my-new-db'); if (newDatabase) { console.log(`Database created with ID: ${newDatabase.id}`); } -const allDatabases = await client.getDatabases(); -if (allDatabases) { - console.log(`Retrieved ${allDatabases.length} databases`); +const { data, error } = await client.getDatabases(); +if (data) { + console.log(`Retrieved ${data.length} databases`); } ``` @@ -169,18 +170,18 @@ if (allDatabases) { ```typescript import { createClient } from 'azion/sql'; -import { AzionSQLClient, AzionDatabase } from 'azion/sql/types'; +import type { AzionSQLClient, AzionDatabaseResponse } from 'azion/sql'; const client: AzionSQLClient = createClient({ token: 'your-api-token', debug: true }); -const newDatabase: AzionDatabase | null = await client.createDatabase('my-new-db'); -if (newDatabase) { - console.log(`Database created with ID: ${newDatabase.id}`); +const { data, error }: AzionDatabaseResponse = await client.createDatabase('my-new-db'); +if (data) { + console.log(`Database created with ID: ${data.id}`); } -const allDatabases: AzionDatabase[] | null = await client.getDatabases(); -if (allDatabases) { - console.log(`Retrieved ${allDatabases.length} databases`); +const allDatabases: AzionDatabaseResponse = await client.getDatabases(); +if (allDatabases.data) { + console.log(`Retrieved ${allDatabases.data.length} databases`); } ``` @@ -270,7 +271,7 @@ The Jwt library provides methods to sign, verify and decode tokens. **JavaScript:** ```javascript -import { sign, verify, decode } from "azion/jwt"; +import { sign, verify, decode } from 'azion/jwt'; const key = 'your-key'; @@ -285,13 +286,13 @@ console.log(`verify result: ${JSON.stringify(verifyResult)}`); // decode const { header, payload } = decode(token); -console.log(`decode result: ${JSON.stringify({header, payload})}`); +console.log(`decode result: ${JSON.stringify({ header, payload })}`); ``` **TypeScript:** ```typescript -import { sign, verify, decode } from "azion/jwt"; +import { sign, verify, decode } from 'azion/jwt'; import type { JWTPayload } from 'azion/jwt'; const key: string = 'your-key'; @@ -306,11 +307,14 @@ const verifyResult: JWTPayload = await verify(token, key); console.log(`verify result: ${JSON.stringify(verifyResult)}`); // decode -const { header, payload }: { +const { + header, + payload, +}: { header: any; payload: JWTPayload; } = decode(token); -console.log(`decode result: ${JSON.stringify({header, payload})}`); +console.log(`decode result: ${JSON.stringify({ header, payload })}`); ``` Read more in the [Jwt README](./packages/jwt/README.md). diff --git a/packages/client/README.MD b/packages/client/README.MD index 44b7f06..c95df6a 100644 --- a/packages/client/README.MD +++ b/packages/client/README.MD @@ -118,19 +118,19 @@ import { createClient } from 'azion'; const client = createClient({ token: 'your-api-token', debug: true }); -const newDatabase = await client.sql.createDatabase('my-new-db'); +const { data: newDatabase, error } = await client.sql.createDatabase('my-new-db'); if (newDatabase) { console.log(`AzionDatabase created with ID: ${newDatabase.id}`); } const allDatabases = await client.sql.getDatabases(); -if (allDatabases) { - console.log(`Retrieved ${allDatabases.length} databases`); +if (allDatabases?.data) { + console.log(`Retrieved ${allDatabases?.data?.length} databases`); } const queryResult = await client.sql.query('SELECT * FROM users'); -if (queryResult) { - console.log(`Query executed. Rows returned: ${queryResult.rows.length}`); +if (queryResult.data) { + console.log(`Query executed. Rows returned: ${queryResult?.data?.rows.length}`); } ``` @@ -138,23 +138,24 @@ if (queryResult) { ```typescript import { createClient } from 'azion'; -import { AzionClient, AzionDatabase, QueryResponse } from 'azion/sql/types'; +import type { AzionClient } from 'azion/client'; +import type { AzionDatabaseResponse, AzionDatabaseQueryResponse } from 'azion/sql'; const client: AzionClient = createClient({ token: 'your-api-token', { debug: true } }); -const newDatabase: AzionDatabase | null = await client.sql.createDatabase('my-new-db'); +const { data: newDatabase, error }: AzionDatabaseResponse = await client.sql.createDatabase('my-new-db'); if (newDatabase) { console.log(`AzionDatabase created with ID: ${newDatabase.id}`); } -const allDatabases: AzionDatabase[] | null = await client.sql.getDatabases(); -if (allDatabases) { - console.log(`Retrieved ${allDatabases.length} databases`); +const allDatabases: AzionDatabaseResponse = await client.sql.getDatabases(); +if (allDatabases?.data) { + console.log(`Retrieved ${allDatabases?.data?.length} databases`); } -const queryResult: QueryResponse | null = await client.sql.query(['SELECT * FROM users']); -if (queryResult) { - console.log(`Query executed. Rows returned: ${queryResult.data.length}`); +const queryResult: AzionDatabaseQueryResponse = await client.sql.query(['SELECT * FROM users']); +if (queryResult?.data) { + console.log(`Query executed. Rows returned: ${queryResult?.data?.length}`); } ``` diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index 62161ce..0accdb9 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -55,11 +55,11 @@ export interface AzionClient { * * @example * // Get all databases - * const allDatabases = await client.sql.getDatabases(); + * const { data, error } = await client.sql.getDatabases(); * * @example * // Get a specific database and perform operations - * const db = await client.sql.getDatabase('my-db'); + * const { data:db, error } = await client.sql.getDatabase('my-db'); * if (db) { * // Execute a query * const queryResult = await db.query(['SELECT * FROM users WHERE id = ?', 1]); @@ -76,7 +76,7 @@ export interface AzionClient { * * @example * // Delete a database - * const deletedDatabase = await client.sql.deleteDatabase(123); // Using database ID + * const { data, error } = await client.sql.deleteDatabase(123); // Using database ID */ sql: AzionSQLClient; diff --git a/packages/sql/README.MD b/packages/sql/README.MD index 308d5b4..1124009 100644 --- a/packages/sql/README.MD +++ b/packages/sql/README.MD @@ -31,11 +31,12 @@ Azion Edge SQL Client provides a simple interface to interact with the Azion Edg - [`ClientConfig`](#clientconfig) - [`AzionSQLClient`](#azionsqlclient) - [`AzionDatabase`](#aziondatabase) - - [`DeletedAzionDatabase`](#deletedaziondatabase) + - [`AzionDatabaseResponse`](#aziondatabaseresponse) - [`QueryResult`](#queryresult) - [`AzionClientOptions`](#azionclientoptions) - [`AzionDatabaseCollectionOptions`](#aziondatabasecollectionoptions) - - [`AzionQueryResponse`](#azionqueryresponse) + - [`AzionDatabaseQueryResponse`](#aziondatabasequeryresponse) + - [`AzionDatabaseExecutionResponse`](#aziondatabaseexecutionresponse) - [`NonSelectQueryResult`](#nonselectqueryresult) - [`AzionQueryExecutionInfo`](#azionqueryexecutioninfo) - [`AzionQueryExecutionParams`](#azionqueryexecutionparams) @@ -97,11 +98,11 @@ You can create a client instance with specific configurations. ```javascript import { createDatabase } from 'azion/sql'; -const database = await createDatabase('my-new-database', { debug: true }); -if (database) { - console.log(`Database created with ID: ${database.id}`); +const { data, error } = await createDatabase('my-new-database', { debug: true }); +if (data) { + console.log(`Database created with ID: ${data.id}`); } else { - console.error('Failed to create database'); + console.error('Failed to create database', error); } ``` @@ -109,12 +110,14 @@ if (database) { ```typescript import { createDatabase, AzionDatabase } from 'azion/sql'; +import type { AzionDatabaseResponse, AzionDatabase } from 'azion/sql'; -const database: AzionDatabase | null = await createDatabase('my-new-database', { debug: true }); -if (database) { +const { data, error }: AzionDatabaseResponse = await createDatabase('my-new-database', { debug: true }); +if (data) { + const database: AzionDatabase = data; console.log(`Database created with ID: ${database.id}`); } else { - console.error('Failed to create database'); + console.error('Failed to create database', error); } ``` @@ -125,24 +128,25 @@ if (database) { ```javascript import { deleteDatabase } from 'azion/sql'; -const result = await deleteDatabase(123, { debug: true }); -if (result) { - console.log(`Database ${result.id} deleted successfully`); +const { data, error } = await deleteDatabase(123, { debug: true }); +if (data) { + console.log(`Database ${data.id} deleted successfully`); } else { - console.error('Failed to delete database'); + console.error('Failed to delete database', error); } ``` **TypeScript:** ```typescript -import { deleteDatabase, AzionDeletedDatabase } from 'azion/sql'; +import { deleteDatabase } from 'azion/sql'; +import type { AzionDatabaseResponse } from 'azion/sql'; -const result: AzionDeletedDatabase | null = await deleteDatabase(123, { debug: true }); -if (result) { - console.log(`Database ${result.id} deleted successfully`); +const { data, error }: AzionDatabaseResponse = await deleteDatabase(123, { debug: true }); +if (data) { + console.log(`Database ${data.id} deleted successfully`); } else { - console.error('Failed to delete database'); + console.error('Failed to delete database', error); } ``` @@ -153,24 +157,26 @@ if (result) { ```javascript import { getDatabase } from 'azion/sql'; -const database = await getDatabase('my-db', { debug: true }); -if (database) { - console.log(`Retrieved database: ${database.id}`); +const { data, error } = await getDatabase('my-db', { debug: true }); +if (data) { + console.log(`Retrieved database: ${data.id}`); } else { - console.error('Database not found'); + console.error('Database not found', error); } ``` **TypeScript:** ```typescript -import { getDatabase, AzionDatabase } from 'azion/sql'; +import { getDatabase } from 'azion/sql'; +import type { AzionDatabaseResponse, AzionDatabase } from 'azion/sql'; -const database: AzionDatabase | null = await getDatabase('my-db', { debug: true }); -if (database) { +const { data, error }: AzionDatabaseResponse = await getDatabase('my-db', { debug: true }); +if (data) { + const database: AzionDatabase = data; console.log(`Retrieved database: ${database.id}`); } else { - console.error('Database not found'); + console.error('Database not found', error); } ``` @@ -181,24 +187,26 @@ if (database) { ```javascript import { getDatabases } from 'azion/sql'; -const databases = await getDatabases({ page: 1, page_size: 10 }, { debug: true }); -if (databases) { - console.log(`Retrieved ${databases.length} databases`); +const { data, error } = await getDatabases({ page: 1, page_size: 10 }, { debug: true }); +if (data) { + console.log(`Retrieved ${data.length} databases`); } else { - console.error('Failed to retrieve databases'); + console.error('Failed to retrieve databases', error); } ``` **TypeScript:** ```typescript -import { getDatabases, AzionDatabase } from 'azion/sql'; +import { getDatabases } from 'azion/sql'; +import type { AzionDatabaseResponse, AzionDatabase } from 'azion/sql'; -const databases: AzionDatabase[] | null = await getDatabases({ page: 1, page_size: 10 }, { debug: true }); -if (databases) { +const { data, error }: AzionDatabaseResponse = await getDatabases({ page: 1, page_size: 10 }, { debug: true }); +if (data) { + const databases: AzionDatabase[] = data; console.log(`Retrieved ${databases.length} databases`); } else { - console.error('Failed to retrieve databases'); + console.error('Failed to retrieve databases', error); } ``` @@ -220,9 +228,9 @@ if (result) { **TypeScript:** ```typescript -import { useQuery, AzionQueryResponse } from 'azion/sql'; +import { useQuery, AzionDatabaseQueryResponse } from 'azion/sql'; -const result: AzionQueryResponse | null = await useQuery('my-db', ['SELECT * FROM users'], { debug: true }); +const result: AzionDatabaseQueryResponse | null = await useQuery('my-db', ['SELECT * FROM users'], { debug: true }); if (result) { console.log(`Query executed. Rows returned: ${result.rows.length}`); } else { @@ -248,11 +256,15 @@ if (result?.state === 'executed') { **TypeScript:** ```typescript -import { useExecute, AzionQueryResponse } from 'azion/sql'; - -const result: AzionQueryResponse | null = await useExecute('my-db', ['INSERT INTO users (name) VALUES ("John")'], { - debug: true, -}); +import { useExecute, AzionDatabaseQueryResponse } from 'azion/sql'; + +const result: AzionDatabaseQueryResponse | null = await useExecute( + 'my-db', + ['INSERT INTO users (name) VALUES ("John")'], + { + debug: true, + }, +); if (result?.state === 'executed') { console.log('Executed with success'); } else { @@ -267,25 +279,27 @@ if (result?.state === 'executed') { ```javascript import { listTables } from 'azion/sql'; -const result = await listTables('my-db', { debug: true }); +const { data, error } = await listTables('my-db', { debug: true }); -if (result) { - console.log(result); +if (data) { + console.log(data); } else { - console.error('Query execution failed'); + console.error('Query execution failed', error); } ``` **TypeScript:** ```typescript -import { listTables, AzionQueryResponse } from 'azion/sql'; +import { listTables } from 'azion/sql'; +import type { AzionDatabaseQueryResponse } from 'azion/sql'; -const result: AzionQueryResponse | null = await listTables('my-db', { debug: true }); -if (result) { - console.log(result); +const { data, error }: AzionDatabaseQueryResponse = await listTables('my-db', { debug: true }); + +if (data) { + console.log(data); } else { - console.error('Query execution failed'); + console.error('Query execution failed', error); } ``` @@ -302,16 +316,16 @@ Creates a new database. **Returns:** -- `Promise` - The created database object or null if creation failed. +- `Promise` - The created database object or error. **Example:** ```javascript -const newDatabase = await sqlClient.createDatabase('my-new-db'); -if (newDatabase) { - console.log(`Database created with ID: ${newDatabase.id}`); +const { data, error } = await sqlClient.createDatabase('my-new-db'); +if (data) { + console.log(`Database created with ID: ${data.id}`); } else { - console.error('Failed to create database'); + console.error('Failed to create database', error); } ``` @@ -326,16 +340,16 @@ Deletes a database by its ID. **Returns:** -- `Promise` - Object confirming deletion or null if deletion failed. +- `Promise` - Object confirming deletion or error. **Example:** ```javascript -const result = await sqlClient.deleteDatabase(123); -if (result) { - console.log(`Database ${result.id} deleted successfully`); +const { data, error } = await sqlClient.deleteDatabase(123); +if (data) { + console.log(`Database ${data.id} deleted successfully`); } else { - console.error('Failed to delete database'); + console.error('Failed to delete database', error); } ``` @@ -350,16 +364,16 @@ Retrieves a database by its Name. **Returns:** -- `Promise` - The retrieved database object or null if not found. +- `Promise` - The retrieved database object or error. **Example:** ```javascript -const database = await sqlClient.getDatabase('my-db'); -if (database) { - console.log(`Retrieved database: ${database.name}`); +const { data, error } = await sqlClient.getDatabase('my-db'); +if (data) { + console.log(`Retrieved database: ${data.name}`); } else { - console.error('Database not found'); + console.error('Database not found', error); } ``` @@ -374,17 +388,17 @@ Retrieves a list of databases with optional filtering and pagination. **Returns:** -- `Promise` - Array of database objects or null if retrieval failed. +- `Promise` - Array of database objects or error. **Example:** ```javascript -const databases = await sqlClient.getDatabases({ page: 1, page_size: 10, search: 'test' }); -if (databases) { - console.log(`Retrieved ${databases.length} databases`); - databases.forEach((db) => console.log(`- ${db.name} (ID: ${db.id})`)); +const { data, error } = await sqlClient.getDatabases({ page: 1, page_size: 10, search: 'test' }); +if (data) { + console.log(`Retrieved ${data.length} databases`); + data.forEach((db) => console.log(`- ${db.name} (ID: ${db.id})`)); } else { - console.error('Failed to retrieve databases'); + console.error('Failed to retrieve databases', error); } ``` @@ -400,16 +414,16 @@ Executes a query on a specific database. **Returns:** -- `Promise` - Query result object or null if execution failed. +- `Promise` - Query result object or error. **Example:** ```javascript -const result = await sqlClient.useQuery('my-db', ['SELECT * FROM users']); -if (result) { - console.log(`Query executed. Rows returned: ${result.rows.length}`); +const { data, error } = await sqlClient.useQuery('my-db', ['SELECT * FROM users']); +if (data) { + console.log(`Query executed. Rows returned: ${data.rows.length}`); } else { - console.error('Query execution failed'); + console.error('Query execution failed', error); } ``` @@ -425,16 +439,16 @@ Executes a set of SQL statements on a specific database. **Returns:** -- `Promise` - Execution result object or null if execution failed. +- `Promise` - Execution result object or error. **Example:** ```javascript -const result = await sqlClient.useExecute('my-db', ['INSERT INTO users (name) VALUES ("John")']); -if (result?.state === 'executed') { +const { data, error } = await sqlClient.useExecute('my-db', ['INSERT INTO users (name) VALUES ("John")']); +if (data) { console.log('Executed with success'); } else { - console.error('Execution failed'); + console.error('Execution failed', error); } ``` @@ -465,12 +479,12 @@ Configuration options for the SQL client. An object with methods to interact with SQL databases. -- `createDatabase: (name: string) => Promise` -- `deleteDatabase: (id: number) => Promise` +- `createDatabase: (name: string) => Promise` +- `deleteDatabase: (id: number) => Promise` - `getDatabase: (name: string) => Promise` -- `getDatabases: (params?: AzionDatabaseCollectionOptions) => Promise` -- `useQuery: (name: string, statements: string[], options?: AzionClientOptions) => Promise` -- `useExecute: (name: string, statements: string[], options?: AzionClientOptions) => Promise` +- `getDatabases: (params?: AzionDatabaseCollectionOptions) => Promise` +- `useQuery: (name: string, statements: string[], options?: AzionClientOptions) => Promise` +- `useExecute: (name: string, statements: string[], options?: AzionClientOptions) => Promise` ### `AzionDatabase` @@ -478,22 +492,21 @@ The database object. - `id: number` - `name: string` -- `client_id: string` +- `clientId: string` - `status: string` -- `created_at: string` -- `updated_at: string` -- `deleted_at: string | null` -- `query: (statements: string[], options?: AzionClientOptions) => Promise` -- `execute: (statements: string[], options?: AzionClientOptions) => Promise` -- `listTables: (options?: AzionClientOptions) => Promise` +- `createdAt: string` +- `updatedAt: string` +- `deletedAt: string | null` +- `query: (statements: string[], options?: AzionClientOptions) => Promise` +- `execute: (statements: string[], options?: AzionClientOptions) => Promise` +- `listTables: (options?: AzionClientOptions) => Promise` -### `DeletedAzionDatabase` +### `AzionDatabaseResponse` -The response object from a delete database request. +The response object from a database operation. -- `id: number` -- `state: 'executed' | 'pending'` -- `data: null` +- `data?: AzionDatabase | Pick` +- `error?: { message: string, operation: string }` ### `QueryResult` @@ -518,12 +531,22 @@ Optional parameters for filtering and pagination. - `page_size?: number` - `search?: string` -### `AzionQueryResponse` +### `AzionDatabaseQueryResponse` + +The response object from a query execution. + +- `state: 'executed' | 'pending' | 'executed-runtime' | 'failed'` +- `data: QueryResult[] | NonSelectQueryResult` +- `error?: { message: string, operation: string }` +- `toObject?: () => JsonObjectQueryExecutionResponse` + +### `AzionDatabaseExecutionResponse` The response object from a query execution. -- `state: 'executed' | 'pending' | 'executed-runtime'` +- `state: 'executed' | 'pending' | 'executed-runtime' | 'failed'` - `data: QueryResult[] | NonSelectQueryResult` +- `error?: { message: string, operation: string }` - `toObject?: () => JsonObjectQueryExecutionResponse` ### `NonSelectQueryResult` diff --git a/packages/sql/src/index.test.ts b/packages/sql/src/index.test.ts index 1a70b0c..1df7970 100644 --- a/packages/sql/src/index.test.ts +++ b/packages/sql/src/index.test.ts @@ -10,7 +10,7 @@ import { } from '../src/index'; import * as servicesApi from '../src/services/api/index'; import * as services from '../src/services/index'; -import { AzionSQLClient } from './types'; +import { AzionDatabase, AzionDatabaseResponse, AzionSQLClient } from './types'; jest.mock('../src/services/api/index'); @@ -49,15 +49,20 @@ describe('SQL Module', () => { it('should successfully create a database', async () => { const mockResponse = { data: { id: 1, name: 'test-db' } }; (servicesApi.postEdgeDatabase as jest.Mock).mockResolvedValue(mockResponse); - const result = await createDatabase('test-db', { debug: mockDebug }); - expect(result).toEqual(expect.objectContaining({ id: 1, name: 'test-db' })); + const result: AzionDatabaseResponse = await createDatabase('test-db', { debug: mockDebug }); + expect(result.data).toEqual(expect.objectContaining({ id: 1, name: 'test-db' })); expect(servicesApi.postEdgeDatabase).toHaveBeenCalledWith(mockToken, 'test-db', true); }); - it('should return null on failure', async () => { - (servicesApi.postEdgeDatabase as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (servicesApi.postEdgeDatabase as jest.Mock).mockResolvedValue({ + state: 'failed', + error: { detail: 'Database already exists' }, + }); const result = await createDatabase('test-db', { debug: mockDebug }); - expect(result).toBeNull(); + expect(result).toEqual({ + error: { message: 'Database already exists', operation: 'create database' }, + }); }); }); @@ -66,17 +71,20 @@ describe('SQL Module', () => { jest.spyOn(console, 'log').mockImplementation(); }); it('should successfully delete a database', async () => { - const mockResponse = { data: { message: 'Database deleted' }, state: 'success' }; - (servicesApi.deleteEdgeDatabase as jest.Mock).mockResolvedValue(mockResponse); + (servicesApi.deleteEdgeDatabase as jest.Mock).mockResolvedValue({ state: 'success' }); const result = await deleteDatabase(1, { debug: mockDebug }); - expect(result).toEqual({ id: 1, data: { message: 'Database deleted' }, state: 'success' }); + expect(result).toEqual({ data: { id: 1 } }); expect(servicesApi.deleteEdgeDatabase).toHaveBeenCalledWith(mockToken, 1, true); }); - it('should return null on failure', async () => { - (servicesApi.deleteEdgeDatabase as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (servicesApi.deleteEdgeDatabase as jest.Mock).mockResolvedValue({ + error: { detail: 'Database not found' }, + }); const result = await deleteDatabase(1, { debug: mockDebug }); - expect(result).toBeNull(); + expect(result).toEqual({ + error: { message: 'Failed to delete database', operation: 'delete database' }, + }); }); }); @@ -88,17 +96,17 @@ describe('SQL Module', () => { const mockResponse = { results: [{ id: 1, name: 'test-db' }] }; (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue(mockResponse); - const result = await getDatabase('test-db', { debug: mockDebug }); - expect(result).toEqual(expect.objectContaining({ id: 1, name: 'test-db' })); + const result = (await getDatabase('test-db', { debug: mockDebug })) as AzionDatabaseResponse; + expect(result.data).toEqual(expect.objectContaining({ id: 1, name: 'test-db' })); expect(servicesApi.getEdgeDatabases).toHaveBeenCalledWith(mockToken, { search: 'test-db' }, true); }); it('should throw an error if database is not found', async () => { (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue({ results: [] }); - await expect(getDatabase('test-db', { debug: mockDebug })).rejects.toThrow( - "Database with name 'test-db' not found", - ); + await expect(getDatabase('test-db', { debug: mockDebug })).resolves.toEqual({ + error: { message: "Database with name 'test-db' not found", operation: 'get database' }, + }); }); }); @@ -115,17 +123,17 @@ describe('SQL Module', () => { }; (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue(mockResponse); - const result = await getDatabases({ page: 1, page_size: 10 }, { debug: mockDebug }); - expect(result).toHaveLength(2); - expect(result![0]).toEqual(expect.objectContaining({ id: 1, name: 'test-db-1' })); + const { data } = await getDatabases({ page: 1, page_size: 10 }, { debug: mockDebug }); + expect(data).toHaveLength(2); + expect((data as AzionDatabase[])[0]).toEqual(expect.objectContaining({ id: 1, name: 'test-db-1' })); expect(servicesApi.getEdgeDatabases).toHaveBeenCalledWith(mockToken, { page: 1, page_size: 10 }, true); }); - it('should return null if retrieval fails', async () => { - (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue(null); + it('should return error if retrieval fails', async () => { + (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue({ state: 'failed' }); - const result = await getDatabases({ page: 1, page_size: 10 }, { debug: mockDebug }); - expect(result).toBeNull(); + const result = (await getDatabases({ page: 1, page_size: 10 }, { debug: mockDebug })) as AzionDatabaseResponse; + expect(result).toEqual({ error: { message: 'Failed to retrieve databases', operation: 'get databases' } }); }); }); @@ -133,6 +141,11 @@ describe('SQL Module', () => { beforeAll(() => { jest.spyOn(console, 'log').mockImplementation(); }); + + afterAll(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (globalThis as any).Azion = {}; + }); it('should successfully execute a query', async () => { const mockResponseDatabases = { results: [ @@ -142,7 +155,7 @@ describe('SQL Module', () => { }; (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue(mockResponseDatabases); const mockResponse = { - state: 'success', + state: 'executed', data: [{ results: { columns: ['id', 'name'], rows: [[1, 'test']] } }], }; (servicesApi.postQueryEdgeDatabase as jest.Mock).mockResolvedValue(mockResponse); @@ -205,16 +218,22 @@ describe('SQL Module', () => { }; (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue(mockResponseDatabases); - await expect(useExecute('test-db', ['SELECT * FROM test'], { debug: mockDebug })).rejects.toThrowError( - 'Only write statements are allowed', - ); + await expect(useExecute('test-db', ['SELECT * FROM test'], { debug: mockDebug })).resolves.toEqual({ + state: 'failed', + error: { message: 'Only write statements are allowed', operation: 'execute database' }, + }); }); - it('should return if query execution fails', async () => { - (servicesApi.postQueryEdgeDatabase as jest.Mock).mockResolvedValue(null); + it('should return error if query execution fails', async () => { + (servicesApi.postQueryEdgeDatabase as jest.Mock).mockResolvedValue({ + state: 'failed', + }); const result = await useQuery('test-db', ['SELECT * FROM test'], { debug: mockDebug }); - expect(result).toEqual({ state: 'executed', data: [] }); + expect(result).toEqual({ + state: 'failed', + error: { message: 'Error executing query', operation: 'executing query' }, + }); }); it('should successfully execute useQuery with apiQuery called', async () => { diff --git a/packages/sql/src/index.ts b/packages/sql/src/index.ts index 2487dc7..7e42966 100644 --- a/packages/sql/src/index.ts +++ b/packages/sql/src/index.ts @@ -4,10 +4,9 @@ import { apiQuery, runtimeQuery } from './services/index'; import { getAzionSql } from './services/runtime/index'; import type { AzionClientOptions, - AzionDatabase, AzionDatabaseCollectionOptions, - AzionDeletedDatabase, - AzionQueryResponse, + AzionDatabaseQueryResponse, + AzionDatabaseResponse, AzionSQLClient, CreateAzionSQLClient, } from './types'; @@ -27,30 +26,36 @@ const createDatabaseMethod = async ( token: string, name: string, options?: AzionClientOptions, -): Promise => { - const apiResponse = await postEdgeDatabase(resolveToken(token), name, resolveDebug(options?.debug)); - if (apiResponse) { - const { data } = apiResponse; - return { - ...data, - query: (statements: string[]) => - queryDatabaseMethod(resolveToken(token), data.name, statements, { - ...options, - debug: resolveDebug(options?.debug), - }), - execute: (statements: string[], options?: AzionClientOptions) => - executeDatabaseMethod(resolveToken(token), data.name, statements, { - ...options, - debug: resolveDebug(options?.debug), - }), - listTables: (options?: AzionClientOptions) => - listTablesWrapper(data.name, { - ...options, - debug: resolveDebug(options?.debug), - }), - }; +): Promise => { + const { data, error } = await postEdgeDatabase(resolveToken(token), name, resolveDebug(options?.debug)); + if (data) { + return Promise.resolve({ + data: { + ...data, + query: (statements: string[]) => + queryDatabaseMethod(resolveToken(token), data.name, statements, { + ...options, + debug: resolveDebug(options?.debug), + }), + execute: (statements: string[], options?: AzionClientOptions) => + executeDatabaseMethod(resolveToken(token), data.name, statements, { + ...options, + debug: resolveDebug(options?.debug), + }), + listTables: (options?: AzionClientOptions) => + listTablesWrapper(data.name, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + } as AzionDatabaseResponse); } - return null; + return { + error: { + message: error?.detail ?? 'Failed to create database', + operation: 'create database', + }, + }; }; /** @@ -58,23 +63,27 @@ const createDatabaseMethod = async ( * @param token Token to authenticate with the API. * @param id ID of the database to delete. * @param debug Debug mode for detailed logging. - * @returns Object confirming deletion or null if deletion failed. + * @returns Object confirming deletion or error if deletion failed. */ const deleteDatabaseMethod = async ( token: string, id: number, options?: AzionClientOptions, -): Promise => { +): Promise => { const apiResponse = await deleteEdgeDatabase(resolveToken(token), id, resolveDebug(options?.debug)); - if (apiResponse) { - const { data, state } = apiResponse; - return { - id, - data, - state, - }; + if (apiResponse?.state) { + return Promise.resolve({ + data: { + id, + }, + }); } - return null; + return Promise.resolve({ + error: { + message: 'Failed to delete database', + operation: 'delete database', + }, + }); }; /** @@ -88,36 +97,53 @@ const getDatabaseMethod = async ( token: string, name: string, options?: AzionClientOptions, -): Promise => { +): Promise => { if (!name || name === '') { - throw new Error('Database name is required'); + return Promise.resolve({ + error: { + message: 'Database name is required', + operation: 'get database', + }, + }); } const databaseResponse = await getEdgeDatabases(resolveToken(token), { search: name }, resolveDebug(options?.debug)); if (!databaseResponse?.results || databaseResponse?.results?.length === 0) { - throw new Error(`Database with name '${name}' not found`); + return Promise.resolve({ + error: { + message: `Database with name '${name}' not found`, + operation: 'get database', + }, + }); } const databaseResult = databaseResponse?.results[0]; if (!databaseResult || databaseResult.id === undefined || databaseResult.name !== name) { - throw new Error(`Database with name '${name}' not found`); + return Promise.resolve({ + error: { + message: `Database with name '${name}' not found`, + operation: 'get database', + }, + }); } - return { - ...databaseResult, - query: (statements: string[], options?: AzionClientOptions) => - queryDatabaseMethod(resolveToken(token), databaseResult.name, statements, { - ...options, - debug: resolveDebug(options?.debug), - }), - execute: (statements: string[], options?: AzionClientOptions) => - executeDatabaseMethod(resolveToken(token), databaseResult.name, statements, { - ...options, - debug: resolveDebug(options?.debug), - }), - listTables: (options?: AzionClientOptions) => - listTablesWrapper(databaseResult.name, { - ...options, - debug: resolveDebug(options?.debug), - }), - }; + return Promise.resolve({ + data: { + ...databaseResult, + query: (statements: string[]) => + queryDatabaseMethod(resolveToken(token), databaseResult.name, statements, { + ...options, + debug: resolveDebug(options?.debug), + }), + execute: (statements: string[], options?: AzionClientOptions) => + executeDatabaseMethod(resolveToken(token), databaseResult.name, statements, { + ...options, + debug: resolveDebug(options?.debug), + }), + listTables: (options?: AzionClientOptions) => + listTablesWrapper(databaseResult.name, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + } as AzionDatabaseResponse); }; /** @@ -125,35 +151,45 @@ const getDatabaseMethod = async ( * @param token Token to authenticate with the API. * @param params Optional parameters for filtering and pagination. * @param debug Debug mode for detailed logging. - * @returns Array of database objects or null if retrieval failed. + * @returns Array of database objects or error if retrieval failed. */ const getDatabasesMethod = async ( token: string, params?: AzionDatabaseCollectionOptions, options?: AzionClientOptions, -): Promise => { +): Promise => { const apiResponse = await getEdgeDatabases(resolveToken(token), params, resolveDebug(options?.debug)); - if (apiResponse) { - return apiResponse.results.map((db: ApiDatabaseResponse) => ({ - ...db, - query: (statements: string[]): Promise => - queryDatabaseMethod(resolveToken(token), db.name, statements, { - ...options, - debug: resolveDebug(options?.debug), - }), - execute: (statements: string[], options?: AzionClientOptions): Promise => - executeDatabaseMethod(resolveToken(token), db.name, statements, { - ...options, - debug: resolveDebug(options?.debug), - }), - listTables: (options?: AzionClientOptions): Promise => - listTablesWrapper(db.name, { - ...options, - debug: resolveDebug(options?.debug), - }), - })); + if (apiResponse?.results && apiResponse.results.length > 0) { + const databases = apiResponse.results.map((db: ApiDatabaseResponse) => { + return { + ...db, + query: (statements: string[]): Promise => + queryDatabaseMethod(resolveToken(token), db.name, statements, { + ...options, + debug: resolveDebug(options?.debug), + }), + execute: (statements: string[], options?: AzionClientOptions): Promise => + executeDatabaseMethod(resolveToken(token), db.name, statements, { + ...options, + debug: resolveDebug(options?.debug), + }), + listTables: (options?: AzionClientOptions): Promise => + listTablesWrapper(db.name, { + ...options, + debug: resolveDebug(options?.debug), + }), + }; + }); + return Promise.resolve({ + data: databases as AzionDatabaseResponse['data'], + }); } - return null; + return Promise.resolve({ + error: { + message: apiResponse?.detail ?? 'Failed to retrieve databases', + operation: 'get databases', + }, + }); }; /** @@ -169,15 +205,27 @@ const queryDatabaseMethod = async ( name: string, statements: string[], options?: AzionClientOptions, -): Promise => { +): Promise => { if (!name || name === '') { - throw new Error('Database name is required'); + return Promise.resolve({ + state: 'failed', + error: { + message: 'Database name is required', + operation: 'query database', + }, + }); } if (options?.debug) { console.log(`Executing statements on database ${name}: ${statements}`); } if (!Array.isArray(statements) || statements.length === 0) { - throw new Error('No statements to execute. Please provide at least one statement. e.g ["SELECT * FROM users"]'); + return Promise.resolve({ + state: 'failed', + error: { + message: 'No statements to execute. Please provide at least one statement. e.g ["SELECT * FROM users"]', + operation: 'query database', + }, + }); } const isStatement = statements.some((statement) => ['SELECT', 'PRAGMA'].some((keyword) => statement.trim().toUpperCase().startsWith(keyword)), @@ -205,17 +253,28 @@ const executeDatabaseMethod = async ( name: string, statements: string[], options?: AzionClientOptions, -): Promise => { +): Promise => { if (options?.debug) { console.log(`Executing statements on database ${name}: ${statements}`); } if (!name || name === '') { - throw new Error('Database name is required'); + return Promise.resolve({ + state: 'failed', + error: { + message: 'Database name is required', + operation: 'execute database', + }, + }); } if (!Array.isArray(statements) || statements.length === 0) { - throw new Error( - 'No statements to execute. Please provide at least one statement. e.g ["INSERT INTO users (name) VALUES (\'John\')"]', - ); + return Promise.resolve({ + state: 'failed', + error: { + message: + 'No statements to execute. Please provide at least one statement. e.g ["INSERT INTO users (name) VALUES (\'John\')"]', + operation: 'execute database', + }, + }); } const isWriteStatement = statements.some((statement) => ['INSERT', 'UPDATE', 'DELETE'].some((keyword) => statement.trim().toUpperCase().startsWith(keyword)), @@ -224,15 +283,27 @@ const executeDatabaseMethod = async ( ['CREATE', 'ALTER', 'DROP', 'TRUNCATE'].some((keyword) => statement.trim().toUpperCase().startsWith(keyword)), ); if (!isAdminStatement && !isWriteStatement) { - throw new Error('Only write statements are allowed'); + return Promise.resolve({ + state: 'failed', + error: { + message: 'Only write statements are allowed', + operation: 'execute database', + }, + }); } if (isAdminStatement && options?.force === false) { - throw new Error('To admin statements, you need to set the force option to true'); + return Promise.resolve({ + state: 'failed', + error: { + message: 'To admin statements, you need to set the force option to true', + operation: 'execute database', + }, + }); } const resultQuery = await apiQuery(token, name, statements, options); return { state: resultQuery.state, - data: {}, + data: resultQuery.data, }; }; @@ -241,17 +312,17 @@ const executeDatabaseMethod = async ( * * @param {string} name - Name of the database to create. * @param {AzionClientOptions} [options] - Optional parameters for the deletion. - * @returns {Promise} The created database object or null if creation failed. + * @returns {Promise} The created database object or error if creation failed. * * @example - * const database = await createDatabase('my-new-database', { debug: true }); - * if (database) { - * console.log(`Database created with ID: ${database.id}`); + * const { data, error } = await createDatabase('my-new-database', { debug: true }); + * if (data) { + * console.log(`Database ${data.id} created successfully`); * } else { - * console.error('Failed to create database'); + * console.error(`Failed to create database: ${error.message}`); * } */ -const createDatabaseWrapper = async (name: string, options?: AzionClientOptions): Promise => +const createDatabaseWrapper = async (name: string, options?: AzionClientOptions): Promise => await createDatabaseMethod(resolveToken(), name, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -259,17 +330,17 @@ const createDatabaseWrapper = async (name: string, options?: AzionClientOptions) * * @param {number} id - ID of the database to delete. * @param {AzionClientOptions} [options] - Optional parameters for the deletion. - * @returns {Promise} Object confirming deletion or null if deletion failed. + * @returns {Promise} Object confirming deletion or error if deletion failed. * * @example - * const result = await deleteDatabase(123, { debug: true }); - * if (result) { - * console.log(`Database ${result.id} deleted successfully`); + * const { data, error } = await deleteDatabase(123, { debug: true }); + * if (data) { + * console.log(`Database ${data.id} deleted successfully`); * } else { - * console.error('Failed to delete database'); - * } + * console.error(`Failed to delete database: ${error.message}`); + * */ -const deleteDatabaseWrapper = (id: number, options?: AzionClientOptions): Promise => +const deleteDatabaseWrapper = (id: number, options?: AzionClientOptions): Promise => deleteDatabaseMethod(resolveToken(), id, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -277,17 +348,17 @@ const deleteDatabaseWrapper = (id: number, options?: AzionClientOptions): Promis * * @param {string} name - Name of the database to retrieve. * @param {AzionClientOptions} [options] - Optional parameters for the deletion. - * @returns {Promise} The retrieved database object or null if not found. + * @returns {Promise} The retrieved database object or error if not found. * * @example - * const database = await getDatabase('my-db', { debug: true }); - * if (database) { - * console.log(`Retrieved database: ${database.id}`); + * const { data, error } = await getDatabase('my-db', { debug: true }); + * if (data) { + * console.log(`Retrieved database ${data.name} (ID: ${data.id})`); * } else { - * console.error('Database not found'); + * console.error(`Failed to retrieve database: ${error.message}`); * } */ -const getDatabaseWrapper = async (name: string, options?: AzionClientOptions): Promise => +const getDatabaseWrapper = async (name: string, options?: AzionClientOptions): Promise => getDatabaseMethod(resolveToken(), name, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -295,20 +366,22 @@ const getDatabaseWrapper = async (name: string, options?: AzionClientOptions): P * * @param {Partial} [params] - Optional parameters for filtering and pagination. * @param {AzionClientOptions} [options] - Optional parameters for the deletion. - * @returns {Promise} Array of database objects or null if retrieval failed. + * @returns {Promise} Array of database objects or error if retrieval failed. * * @example - * const databases = await getDatabases({ page: 1, page_size: 10 }, { debug: true }); - * if (databases) { - * console.log(`Retrieved ${databases.length} databases`); + * const { data, error } = await getDatabases({ page: 1, page_size: 10 }, { debug: true }); + * if (data) { + * console.log(`Retrieved ${data.length} databases`); + * data.forEach(db => console.log(`- ${db.name} (ID: ${db.id})`)); * } else { - * console.error('Failed to retrieve databases'); + * console.error('Failed to retrieve databases', error); * } + * */ const getDatabasesWrapper = ( params?: Partial, options?: AzionClientOptions, -): Promise => +): Promise => getDatabasesMethod(resolveToken(), params, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -320,7 +393,7 @@ const getDatabasesWrapper = ( const listTablesWrapper = async ( databaseName: string, options?: AzionClientOptions, -): Promise => { +): Promise => { return queryDatabaseMethod(resolveToken(), databaseName, ['PRAGMA table_list'], { ...options, debug: resolveDebug(options?.debug), @@ -336,20 +409,18 @@ const listTablesWrapper = async ( * @param options.force Force the query execution. * @returns The query response object or null if the query failed. * @example - * const queryResult = await useQuery('my-db', ['SELECT * FROM users']); - * if (queryResult) { - * console.log(`Query executed with ${queryResult.data.length} statements`); - * const resultObject = queryResult.toObject(); - * console.log(`Result: ${resultObject}`); + * const { data, error } = await useQuery('my-db', ['SELECT * FROM users']); + * if (data) { + * console.log(`Query executed with success`, data); * } else { - * console.error('Failed to execute query'); - * } + * console.error(`Failed to execute query: ${error.message}`); + * */ const useQuery = ( name: string, statements: string[], options?: AzionClientOptions, -): Promise => +): Promise => queryDatabaseMethod(resolveToken(), name, statements, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -359,17 +430,20 @@ const useQuery = ( * @param options.debug Debug mode for detailed logging. * @param options.force Force the query execution. * @returns The query response object or null if the query failed. + * * @example - * const executeResult = await useExecute('my-db', ['INSERT INTO users (name) VALUES ("John")']); - * if (executeResult?.state === 'executed') { - * console.log(`Executed with success`); - * } + * const { data, error } = await useExecute('my-db', ['INSERT INTO users (name) VALUES ("John")']); + * if (data) { + * console.log(`Statements executed with success`, data); + * } else { + * console.error(`Failed to execute statements: `, error); + * */ const useExecute = async ( name: string, statements: string[], options?: AzionClientOptions, -): Promise => +): Promise => executeDatabaseMethod(resolveToken(), name, statements, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -382,7 +456,7 @@ const useExecute = async ( * const sqlClient = createClient({ token: 'your-api-token', options: { debug: true } }); * * // Create a new database - * const newDatabase = await sqlClient.createDatabase('my-new-db'); + * const { data: newDatabase } = await sqlClient.createDatabase('my-new-db'); * * // Get all databases * const allDatabases = await sqlClient.getDatabases(); @@ -397,13 +471,13 @@ const client: CreateAzionSQLClient = ( const debugValue = resolveDebug(config?.options?.debug); const client: AzionSQLClient = { - createDatabase: (name: string): Promise => + createDatabase: (name: string): Promise => createDatabaseMethod(tokenValue, name, { ...config, debug: debugValue }), - deleteDatabase: (id: number): Promise => + deleteDatabase: (id: number): Promise => deleteDatabaseMethod(tokenValue, id, { ...config, debug: debugValue }), - getDatabase: (name: string): Promise => + getDatabase: (name: string): Promise => getDatabaseMethod(tokenValue, name, { ...config, debug: debugValue }), - getDatabases: (params?: AzionDatabaseCollectionOptions): Promise => + getDatabases: (params?: AzionDatabaseCollectionOptions): Promise => getDatabasesMethod(tokenValue, params, { ...config, debug: debugValue }), } as const; diff --git a/packages/sql/src/services/api/index.ts b/packages/sql/src/services/api/index.ts index 8f1c612..ed0385d 100644 --- a/packages/sql/src/services/api/index.ts +++ b/packages/sql/src/services/api/index.ts @@ -14,13 +14,9 @@ const BASE_URL = 'https://api.azion.com/v4/edge_sql/databases'; * @param {string} token - The authorization token. * @param {string} name - The name of the database. * @param {boolean} [debug] - Optional debug flag. - * @returns {Promise} The response from the API or null if an error occurs. + * @returns {ApiCreateDatabaseResponse} The response from the API. */ -const postEdgeDatabase = async ( - token: string, - name: string, - debug?: boolean, -): Promise => { +const postEdgeDatabase = async (token: string, name: string, debug?: boolean): Promise => { try { const response = await fetch(BASE_URL, { method: 'POST', @@ -30,12 +26,32 @@ const postEdgeDatabase = async ( }, body: JSON.stringify({ name }), }); - const data = await response.json(); - if (debug) console.log('Response', data); - return data; + const result = await response.json(); + if (debug) console.log('Response Post Database', JSON.stringify(result)); + if (result?.detail) { + return Promise.resolve({ + state: 'failed', + error: { + detail: result.detail, + }, + }); + } + return Promise.resolve({ + state: result.state, + data: { + clientId: result.data.client_id, + createdAt: result.data.created_at, + deletedAt: result.data.deleted_at, + id: result.data.id, + isActive: result.data.is_active, + name: result.data.name, + status: result.data.status, + updatedAt: result.data.updated_at, + }, + }); } catch (error) { if (debug) console.error('Error creating EdgeDB:', error); - return null; + throw error; } }; @@ -46,11 +62,7 @@ const postEdgeDatabase = async ( * @param {boolean} [debug] - Optional debug flag. * @returns {Promise} The response from the API or null if an error occurs. */ -const deleteEdgeDatabase = async ( - token: string, - id: number, - debug?: boolean, -): Promise => { +const deleteEdgeDatabase = async (token: string, id: number, debug?: boolean): Promise => { try { const response = await fetch(`${BASE_URL}/${id}`, { method: 'DELETE', @@ -58,11 +70,11 @@ const deleteEdgeDatabase = async ( Authorization: `Token ${token}`, }, }); - if (debug) console.log('Response:', response); + if (debug) console.log('Response Delete Database:', response); return response.json(); } catch (error) { if (debug) console.error('Error deleting EdgeDB:', error); - return null; + throw error; } }; @@ -72,14 +84,14 @@ const deleteEdgeDatabase = async ( * @param {number} id - The ID of the database to query. * @param {string[]} statements - The SQL statements to execute. * @param {boolean} [debug] - Optional debug flag. - * @returns {Promise} The response from the API or null if an error occurs. + * @returns {Promise} The response from the API or error if an error occurs. */ const postQueryEdgeDatabase = async ( token: string, id: number, statements: string[], debug?: boolean, -): Promise => { +): Promise => { try { const response = await fetch(`${BASE_URL}/${id}/query`, { method: 'POST', @@ -92,14 +104,24 @@ const postQueryEdgeDatabase = async ( if (!response.ok) { if (debug) console.error('Error querying EdgeDB:', response.statusText); - throw new Error(`Error querying EdgeDB: ${response.statusText}`); + return { + state: 'failed', + error: { + detail: response.statusText, + }, + }; } const json = await response.json(); if (json.error) { if (debug) console.error('Error querying EdgeDB:', json.error); - throw new Error(json.error); + return Promise.resolve({ + state: 'failed', + error: { + detail: json.error, + }, + }); } if (debug) { @@ -118,7 +140,10 @@ const postQueryEdgeDatabase = async ( }; console.log('Response Query:', JSON.stringify(limitedData)); } - return json; + return Promise.resolve({ + state: json.state, + data: json.data, + }); } catch (error) { if (debug) console.error('Error querying EdgeDB:', error); throw new Error((error as Error)?.message); @@ -158,13 +183,13 @@ const getEdgeDatabaseById = async ( * @param {string} token - The authorization token. * @param {Partial} [params] - Optional query parameters. * @param {boolean} [debug] - Optional debug flag. - * @returns {Promise} The response from the API or null if an error occurs. + * @returns {Promise} The response from the API or error. */ const getEdgeDatabases = async ( token: string, params?: Partial, debug?: boolean, -): Promise => { +): Promise => { try { const url = new URL(BASE_URL); if (params) { @@ -189,10 +214,31 @@ const getEdgeDatabases = async ( }; console.log('Response Databases:', JSON.stringify(limitedData)); } - return data; + if (data?.detail) { + return { + results: [], + detail: data.detail, + count: 0, + }; + } + return { + links: data?.links, + count: data.count, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + results: data.results.map((result: any) => ({ + clientId: result.client_id, + createdAt: result.created_at, + deletedAt: result.deleted_at, + id: result.id, + isActive: result.is_active, + name: result.name, + status: result.status, + updatedAt: result.updated_at, + })), + }; } catch (error) { if (debug) console.error('Error getting all EdgeDBs:', error); - return null; + throw error; } }; diff --git a/packages/sql/src/services/api/types.ts b/packages/sql/src/services/api/types.ts index 9ae9702..cf08883 100644 --- a/packages/sql/src/services/api/types.ts +++ b/packages/sql/src/services/api/types.ts @@ -2,17 +2,17 @@ export interface ApiDatabaseResponse { id: number; name: string; - client_id: string; + clientId: string; status: string; - created_at: string; - updated_at: string; - deleted_at: string | null; - is_active: boolean; + createdAt: string; + updatedAt: string; + deletedAt: string | null; + isActive: boolean; } export interface ApiListDatabasesResponse { count: number; - links: { + links?: { first: string | null; last: string | null; next: string | null; @@ -22,8 +22,8 @@ export interface ApiListDatabasesResponse { } export interface ApiQueryExecutionResponse { - state: 'executed' | 'pending'; - data: { + state: 'executed' | 'pending' | 'failed'; + data?: { results: { columns: string[]; rows: (number | string)[][]; @@ -32,11 +32,17 @@ export interface ApiQueryExecutionResponse { query_duration_ms?: number; }; }[]; + error?: { + detail: string; + }; } export interface ApiCreateDatabaseResponse { - state: 'executed' | 'pending'; - data: ApiDatabaseResponse; + state: 'executed' | 'pending' | 'failed'; + data?: ApiDatabaseResponse; + error?: { + detail: string; + }; } export interface ApiDeleteDatabaseResponse { diff --git a/packages/sql/src/services/index.ts b/packages/sql/src/services/index.ts index 47ce291..6b631df 100644 --- a/packages/sql/src/services/index.ts +++ b/packages/sql/src/services/index.ts @@ -1,5 +1,5 @@ -import { AzionClientOptions, AzionQueryResponse, QueryResult } from '../types'; -import { limitArraySize, retryWithBackoff } from '../utils'; +import { AzionClientOptions, AzionDatabaseQueryResponse, QueryResult } from '../types'; +import { limitArraySize } from '../utils'; import { toObjectQueryExecutionResponse } from '../utils/mappers/to-object'; import { getEdgeDatabases, postQueryEdgeDatabase } from './api/index'; import { InternalAzionSql } from './runtime/index'; @@ -10,32 +10,56 @@ export const apiQuery = async ( name: string, statements: string[], options?: AzionClientOptions, -): Promise => { +): Promise => { const databaseResponse = await getEdgeDatabases(token, { search: name }, options?.debug); - if (!databaseResponse?.results || databaseResponse?.results?.length === 0) { - throw new Error(`Database ${name} not found`); + + if (databaseResponse?.detail) { + return { + state: 'failed', + error: { + message: databaseResponse.detail, + operation: 'apiQuery', + }, + }; } - const database = databaseResponse?.results[0]; - if (!database || database?.id === undefined) { - throw new Error(`Database ${name} not found`); + + const databases = databaseResponse?.results; + if (!databases || databases.length === 0) { + return { + state: 'failed', + error: { + message: `Database ${name} not found`, + operation: 'apiQuery', + }, + }; } - const apiResponse = await retryWithBackoff(() => - postQueryEdgeDatabase(token, database.id, statements, options?.debug), - ); - if (apiResponse) { - const resultStatements: AzionQueryResponse = { - state: 'executed', - data: apiResponse.data.map((result, index) => { - let info; - if (result?.results?.query_duration_ms) { - info = { - durationMs: result?.results?.query_duration_ms, - rowsRead: result?.results?.rows_read, - rowsWritten: result?.results?.rows_written, - }; - } + + const database = databases[0]; + if (!database?.id) { + return { + state: 'failed', + error: { + message: `Database ${name} not found`, + operation: 'apiQuery', + }, + }; + } + + const { state, data, error } = await postQueryEdgeDatabase(token, database.id, statements, options?.debug); + if (data) { + const resultStatements: AzionDatabaseQueryResponse = { + state, + data: data.map((result, index) => { + const info = result?.results?.query_duration_ms + ? { + durationMs: result.results.query_duration_ms, + rowsRead: result.results.rows_read, + rowsWritten: result.results.rows_written, + } + : undefined; + return { - statement: statements[index]?.split(' ')[0], // TODO: This can improve + statement: statements[index]?.split(' ')[0], columns: result?.results?.columns, rows: result?.results?.rows, info, @@ -45,9 +69,13 @@ export const apiQuery = async ( }; return resultStatements; } + return { - state: 'executed', - data: [], + state, + error: { + message: error?.detail || 'Error executing query', + operation: 'executing query', + }, }; }; @@ -59,33 +87,43 @@ export const runtimeQuery = async ( statements: string[], // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: AzionClientOptions, -): Promise => { - const internalSql = new InternalAzionSql(); - const internalResult = await retryWithBackoff(() => internalSql.query(name, statements, options)); - const resultStatements: AzionQueryResponse = { - state: 'executed-runtime', - data: [], - }; - const data = await internalSql.mapperQuery(internalResult); - if (data && data.length > 0) { - resultStatements.state = 'executed-runtime'; - resultStatements.data = data; - } - if (options?.debug) { - // limit the size of the array to 10 - const limitedData: AzionQueryResponse = { +): Promise => { + try { + const internalSql = new InternalAzionSql(); + const internalResult = await internalSql.query(name, statements, options); + const resultStatements: AzionDatabaseQueryResponse = { + state: 'executed-runtime', + data: [], + }; + const data = await internalSql.mapperQuery(internalResult); + if (data && data.length > 0) { + resultStatements.state = 'executed-runtime'; + resultStatements.data = data; + } + if (options?.debug) { + // limit the size of the array to 10 + const limitedData: AzionDatabaseQueryResponse = { + ...resultStatements, + data: (resultStatements.data as QueryResult[]).map((data) => { + return { + ...data, + rows: limitArraySize(data?.rows || [], 10), + }; + }), + }; + console.log('Response Query:', JSON.stringify(limitedData)); + } + return { ...resultStatements, - data: (resultStatements.data as QueryResult[]).map((data) => { - return { - ...data, - rows: limitArraySize(data?.rows || [], 10), - }; - }), + toObject: () => toObjectQueryExecutionResponse(resultStatements), + }; + } catch (error) { + return { + state: 'failed', + error: { + message: (error as Error)?.message || 'Error executing query', + operation: 'executing query', + }, }; - console.log('Response Query:', JSON.stringify(limitedData)); } - return { - ...resultStatements, - toObject: () => toObjectQueryExecutionResponse(resultStatements), - }; }; diff --git a/packages/sql/src/types.ts b/packages/sql/src/types.ts index 0695fc2..b505dde 100644 --- a/packages/sql/src/types.ts +++ b/packages/sql/src/types.ts @@ -1,20 +1,28 @@ import { JsonObjectQueryExecutionResponse } from './utils/mappers/to-object'; +export type AzionDatabaseResponse = { + data?: AzionDatabase | AzionDatabase[] | Pick; + error?: { + message: string; + operation: string; + }; +}; + /* eslint-disable no-unused-vars */ export interface AzionDatabase { id: number; name: string; - client_id: string; + clientId: string; status: string; - created_at: string; - updated_at: string; - deleted_at: string | null; + createdAt: string; + updatedAt: string; + deletedAt: string | null; /** * Executes a query or multiple queries on the database. * * @param {string[]} statements - An array of SQL statements to execute. * @param {AzionClientOptions} [options] - Additional options for the query execution. - * @returns {Promise} A promise that resolves to the query response or null if the execution failed. + * @returns {Promise} A promise that resolves to the query response or error if the operation failed. * * @example * const result = await database.query([ @@ -22,14 +30,14 @@ export interface AzionDatabase { * 'UPDATE users SET last_login = NOW() WHERE id = ?' * ], { debug: true }); */ - query?: (statements: string[], options?: AzionClientOptions) => Promise; + query?: (statements: string[], options?: AzionClientOptions) => Promise; /** * Executes one or more SQL statements on the database. * * @param {string[]} statements - An array of SQL statements to execute. * @param {AzionClientOptions} [options] - Additional options for the execution. - * @returns {Promise} A promise that resolves to the execution response or null if the execution failed. + * @returns {Promise} A promise that resolves to the query response or error if the operation failed. * * @example * const result = await database.execute([ @@ -37,24 +45,18 @@ export interface AzionDatabase { * 'DELETE FROM old_users WHERE last_login < ?' * ], { force: true }); */ - execute?: (statements: string[], options?: AzionClientOptions) => Promise; + execute?: (statements: string[], options?: AzionClientOptions) => Promise; /** * Retrieves a list of tables in the database. * * @param {AzionClientOptions} [options] - Additional options for listing tables. - * @returns {Promise} A promise that resolves to the list of tables or null if the operation failed. + * @returns {Promise} A promise that resolves to the query response or error if the operation failed. * * @example * const tables = await database.listTables({ debug: true }); */ - listTables?: (options?: AzionClientOptions) => Promise; -} - -export interface AzionDeletedDatabase { - id: number; - state: 'executed' | 'pending'; - data: null; + listTables?: (options?: AzionClientOptions) => Promise; } export type AzionQueryParams = string | number | boolean | null; @@ -81,12 +83,18 @@ export type QueryResult = { info?: AzionQueryExecutionInfo; }; -export type AzionQueryResponse = { - state: 'executed' | 'pending' | 'executed-runtime'; - data: QueryResult[] | NonSelectQueryResult; +export type AzionDatabaseQueryResponse = { + state: 'executed' | 'pending' | 'executed-runtime' | 'failed'; + data?: QueryResult[] | NonSelectQueryResult | undefined; toObject?: () => JsonObjectQueryExecutionResponse; + error?: { + message: string; + operation: string; + }; }; +export type AzionDatabaseExecutionResponse = AzionDatabaseQueryResponse; + export type AzionDatabaseCollectionOptions = { ordering?: string; page?: number; @@ -96,50 +104,50 @@ export type AzionDatabaseCollectionOptions = { export interface AzionSQLClient { /** - * Deletes a database by its ID. + * Creates a new database with the specified name. * - * @param {number} id - ID of the database to delete. - * @returns {Promise} Object confirming deletion or null if deletion failed. + * @param {string} name - Name of the database to create. + * @returns {Promise} Object confirming creation of the database or an error message. * * @example - * const result = await sqlClient.deleteDatabase(123); - * if (result) { - * console.log(`Database ${result.id} deleted successfully`); + * const { data, error } = await sqlClient.createDatabase('my-db'); + * if (data) { + * console.log(`Database ${data.name} created successfully`); * } else { - * console.error('Failed to delete database'); - * } + * console.error(`Failed to create database: ${error.message}`); + * */ - createDatabase: (name: string) => Promise; + createDatabase: (name: string) => Promise; /** * Deletes a database by its ID. * * @param {number} id - ID of the database to delete. - * @returns {Promise} Object confirming deletion or null if deletion failed. + * @returns {Promise} Object confirming deletion or error if the operation failed. * * @example - * const result = await sqlClient.deleteDatabase(123); - * if (result) { - * console.log(`Database ${result.id} deleted successfully`); + * const { data, error } = await sqlClient.deleteDatabase(123); + * if (data) { + * console.log(`Database ${data.name} (ID: ${data.id}) deleted successfully`); * } else { - * console.error('Failed to delete database'); - * } + * console.error(`Failed to delete database: ${error.message}`); + * */ - deleteDatabase: (id: number) => Promise; + deleteDatabase: (id: number) => Promise; /** * Retrieves a database by its Name. * * @param {string} name - Name of the database to retrieve. - * @returns {Promise} The retrieved database object or null if not found. + * @returns {Promise} The retrieved database object or null if not found. * * @example - * const database = await sqlClient.getDatabase('my-db'); - * if (database) { - * console.log(`Retrieved database: ${database.name}`); + * const { data, error } = await sqlClient.getDatabase('my-db'); + * if (data) { + * console.log(`Retrieved database ${data.name} (ID: ${data.id})`); * } else { - * console.error('Database not found'); - * } + * console.error(`Failed to retrieve database: ${error.message}`); + * */ - getDatabase?: (name: string) => Promise; + getDatabase?: (name: string) => Promise; /** * Retrieves a list of databases with optional filtering and pagination. @@ -149,23 +157,22 @@ export interface AzionSQLClient { * @param {number} [params.page] - Page number for pagination. * @param {number} [params.page_size] - Number of items per page. * @param {string} [params.search] - Search term to filter databases. - * @returns {Promise} Array of database objects or null if retrieval failed. + * @returns {Promise} Array of database objects or error message. * * @example - * const databases = await sqlClient.getDatabases({ page: 1, page_size: 10, search: 'test' }); - * if (databases) { - * console.log(`Retrieved ${databases.length} databases`); - * databases.forEach(db => console.log(`- ${db.name} (ID: ${db.id})`)); + * const { data, error } = await sqlClient.getDatabases({ page: 1, page_size: 10, search: 'test' }); + * if (data) { + * console.log(`Retrieved ${data.length} databases`); * } else { - * console.error('Failed to retrieve databases'); - * } + * console.error(`Failed to retrieve databases: ${error.message}`); + * */ getDatabases: (params?: { ordering?: string; page?: number; page_size?: number; search?: string; - }) => Promise; + }) => Promise; } /** diff --git a/packages/sql/src/utils/mappers/to-object.ts b/packages/sql/src/utils/mappers/to-object.ts index 6c125e0..b2a6478 100644 --- a/packages/sql/src/utils/mappers/to-object.ts +++ b/packages/sql/src/utils/mappers/to-object.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { AzionQueryResponse } from '../../types'; +import { AzionDatabaseQueryResponse } from '../../types'; export type JsonObjectQueryExecutionResponse = { - state: 'executed' | 'pending' | 'executed-runtime'; + state: 'executed' | 'pending' | 'executed-runtime' | 'failed'; data: { statement?: string; rows: { [key: string]: any }[]; @@ -14,7 +14,7 @@ export type JsonObjectQueryExecutionResponse = { }; }; -export const toObjectQueryExecutionResponse = ({ state, data }: AzionQueryResponse) => { +export const toObjectQueryExecutionResponse = ({ state, data }: AzionDatabaseQueryResponse) => { let transformedData: any = []; if (data instanceof Array) { if (data.length === 0) { From c1e667b469a602f96584e680b2010c3544f37226 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 30 Aug 2024 14:15:57 +0000 Subject: [PATCH 11/45] chore(release): 1.7.0-stage.1 [skip ci] ## [1.7.0-stage.1](https://github.com/aziontech/lib/compare/v1.6.0...v1.7.0-stage.1) (2024-08-30) ### Features * jsdoc for azion client ([a4a2bd3](https://github.com/aziontech/lib/commit/a4a2bd39a2e01184aaf0bbf320c194d2364853e6)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57ab3b5..460829c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.7.0-stage.1](https://github.com/aziontech/lib/compare/v1.6.0...v1.7.0-stage.1) (2024-08-30) + + +### Features + +* jsdoc for azion client ([a4a2bd3](https://github.com/aziontech/lib/commit/a4a2bd39a2e01184aaf0bbf320c194d2364853e6)) + ## [1.6.0](https://github.com/aziontech/lib/compare/v1.5.0...v1.6.0) (2024-08-28) diff --git a/package.json b/package.json index 2288aab..50109d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.6.0", + "version": "1.7.0-stage.1", "description": "Azion Packages for Edge Computing.", "scripts": { "prepare": "husky", From 0670adb9b4f9086126a8f5387f95e001da3a748b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 2 Sep 2024 22:13:36 -0300 Subject: [PATCH 12/45] feat: azion cli binary (#32) * feat: azion cli (golang) * refactor: use env * chore: gitignore bin --- .gitignore | 1 + cli/scripts/download-cli.mjs | 184 ++++++++++++++ package-lock.json | 461 +++++++++++++++++++++++++++++++++-- package.json | 11 +- 4 files changed, 633 insertions(+), 24 deletions(-) create mode 100644 cli/scripts/download-cli.mjs diff --git a/.gitignore b/.gitignore index dd4ed36..410c2b0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist coverage .env .DS_Store +cli/bin \ No newline at end of file diff --git a/cli/scripts/download-cli.mjs b/cli/scripts/download-cli.mjs new file mode 100644 index 0000000..4c12af9 --- /dev/null +++ b/cli/scripts/download-cli.mjs @@ -0,0 +1,184 @@ +import chalk from 'chalk'; +import { execSync } from 'child_process'; +import fs from 'fs'; +import https from 'https'; +import os from 'os'; +import path from 'path'; +import ProgressBar from 'progress'; +import { fileURLToPath } from 'url'; + +const defaultVersion = '1.37.0'; +const version = process.env.AZION_CLI_VERSION || defaultVersion; + +const baseUrl = `https://github.com/aziontech/azion/releases/download/${version}`; +const currentDir = path.dirname(fileURLToPath(import.meta.url)); +const packageRoot = path.resolve(currentDir, '..'); +const binDir = path.join(packageRoot, 'bin'); + +const log = { + info: (msg) => console.log(chalk.blue('ℹ ') + msg), + success: (msg) => console.log(chalk.green('✔ ') + msg), + warning: (msg) => console.log(chalk.yellow('⚠ ') + msg), + error: (msg) => console.error(chalk.red('✖ ') + msg), + highlight: (msg) => console.log(chalk.hex('#FFA500')('🚀 ' + msg)), + url: (msg) => chalk.hex('#0000AA')(msg), +}; + +function getPlatform() { + const platform = os.platform(); + const arch = os.arch(); + + if (platform === 'darwin') { + return { os: 'darwin', arch: arch === 'x64' ? 'amd64' : 'arm64', ext: 'tar.gz' }; + } else if (platform === 'linux') { + return { os: 'linux', arch: arch === 'x64' ? 'amd64' : 'arm64', ext: 'tar.gz' }; + } else if (platform === 'win32') { + return { os: 'windows', arch: 'amd64', ext: 'zip' }; + } else { + throw new Error(`Unsupported platform: ${platform}`); + } +} + +function downloadFile(url, dest) { + return new Promise((resolve, reject) => { + log.info(`Downloading from ${chalk.blackBright(url)}`); + https + .get(url, (response) => { + if (response.statusCode === 302) { + // Handle redirect + return downloadFile(response.headers.location, dest).then(resolve).catch(reject); + } + const len = parseInt(response.headers['content-length'], 10); + const bar = new ProgressBar('[:bar] :percent :etas', { + complete: '=', + incomplete: ' ', + width: 20, + total: len, + }); + + const file = fs.createWriteStream(dest); + response.pipe(file); + + response.on('data', (chunk) => { + bar.tick(chunk.length); + }); + + file.on('finish', () => { + file.close(); + const fileSize = fs.statSync(dest).size; + log.success(`File downloaded: ${chalk.cyan(dest)}`); + log.info(`File size: ${chalk.yellow(fileSize)} bytes`); + if (fileSize === 0) { + reject(new Error('The downloaded file is empty')); + } else { + resolve(); + } + }); + }) + .on('error', (err) => { + fs.unlink(dest, () => reject(err)); + }); + }); +} + +function listDirectoryContents(dir) { + const files = fs.readdirSync(dir); + log.info(`Directory contents ${chalk.cyan(dir)}:`); + files.forEach((file) => { + const filePath = path.join(dir, file); + const stats = fs.statSync(filePath); + console.log( + ` ${chalk.cyan(file)} (${stats.isDirectory() ? 'directory' : 'file'}, ${chalk.yellow(stats.size)} bytes)`, + ); + }); +} + +async function downloadAndExtract(platform) { + const fileName = `azion_${version}_${platform.os}_${platform.arch}.${platform.ext}`; + const url = `${baseUrl}/${fileName}`; + const filePath = path.join(binDir, fileName); + + try { + await downloadFile(url, filePath); + + log.info(`Extracting ${chalk.cyan(fileName)}...`); + log.info(`File path: ${chalk.cyan(filePath)}`); + log.info(`Destination directory: ${chalk.cyan(binDir)}`); + + try { + if (platform.ext === 'tar.gz') { + log.info('Executing tar command...'); + execSync(`tar -xzvf "${filePath}" -C "${binDir}"`, { stdio: 'inherit' }); + } else if (platform.ext === 'zip') { + log.info('Executing unzip command...'); + execSync(`unzip -o "${filePath}" -d "${binDir}"`, { stdio: 'inherit' }); + } + } catch (error) { + log.error(`Error extracting file: ${error.message}`); + log.info('File contents:'); + execSync(`file "${filePath}"`, { stdio: 'inherit' }); + throw error; + } + + log.success('Extraction completed. Verifying directory contents:'); + listDirectoryContents(binDir); + + fs.unlinkSync(filePath); + log.info(`Compressed file removed: ${chalk.cyan(filePath)}`); + + // Find the extracted binary + const files = fs.readdirSync(binDir); + const extractedBinary = files.find((file) => file.startsWith('azion')); + + if (!extractedBinary) { + log.error('Extracted binary not found. Directory contents:'); + listDirectoryContents(binDir); + throw new Error('Extracted binary not found'); + } + + const extractedPath = path.join(binDir, extractedBinary); + const finalName = platform.os === 'windows' ? 'azion.exe' : 'azion'; + const finalPath = path.join(binDir, finalName); + + // Rename the extracted binary + fs.renameSync(extractedPath, finalPath); + log.success(`Binary renamed from ${chalk.cyan(extractedPath)} to ${chalk.cyan(finalPath)}`); + + // Set execution permissions on Unix + if (platform.os !== 'windows') { + fs.chmodSync(finalPath, '755'); + log.success(`Execution permissions set for ${chalk.cyan(finalPath)}`); + } + + log.success(`Azion CLI installed at: ${chalk.cyan(finalPath)}`); + } catch (error) { + log.error(`Error during download or extraction: ${error.message}`); + if (fs.existsSync(filePath)) { + log.info(`Removing corrupted file: ${chalk.cyan(filePath)}`); + fs.unlinkSync(filePath); + } + throw error; + } +} + +async function main() { + try { + log.highlight(`Installing Azion CLI v${version}`); + console.log(); + if (!fs.existsSync(binDir)) { + fs.mkdirSync(binDir, { recursive: true, mode: 0o755 }); + log.success(`Directory created: ${chalk.cyan(binDir)}`); + } + + const platform = getPlatform(); + log.info(`Detected platform: ${chalk.cyan(JSON.stringify(platform))}`); + await downloadAndExtract(platform); + log.highlight('Installation completed successfully!'); + process.exit(0); + } catch (error) { + log.error(`Error during installation: ${error.message}`); + process.exit(1); + } +} + +main(); diff --git a/package-lock.json b/package-lock.json index 1ad0c29..1d62703 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,24 @@ { "name": "azion", - "version": "1.5.0-stage.1", + "version": "1.7.0-stage.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "azion", - "version": "1.5.0-stage.1", + "version": "1.7.0-stage.1", + "hasInstallScript": true, "license": "MIT", "workspaces": [ "packages/*" ], + "dependencies": { + "chalk": "^5.3.0", + "progress": "^2.0.3" + }, + "bin": { + "azion": "cli/bin/azion" + }, "devDependencies": { "@commitlint/cli": "^18.4.1", "@commitlint/config-conventional": "^18.4.0", @@ -761,6 +769,22 @@ "node": ">=v18" } }, + "node_modules/@commitlint/format/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@commitlint/is-ignored": { "version": "18.6.1", "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-18.6.1.tgz", @@ -811,6 +835,22 @@ "node": ">=v18" } }, + "node_modules/@commitlint/load/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@commitlint/message": { "version": "18.6.1", "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-18.6.1.tgz", @@ -915,6 +955,22 @@ "node": ">=v18" } }, + "node_modules/@commitlint/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1673,6 +1729,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/console/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -1744,6 +1816,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/core/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -1892,6 +1980,22 @@ } } }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/reporters/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -1992,6 +2096,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/transform/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -2018,6 +2138,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -3997,6 +4133,22 @@ "@babel/core": "^7.8.0" } }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/babel-jest/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4305,16 +4457,11 @@ } }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" @@ -4668,6 +4815,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/create-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -5257,6 +5420,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/eslint/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -6445,6 +6624,22 @@ "node": ">=10" } }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/java-properties": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", @@ -6525,6 +6720,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-circus/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -6567,6 +6778,22 @@ } } }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-config": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", @@ -6612,6 +6839,22 @@ } } }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-config/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -6636,6 +6879,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-docblock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", @@ -6664,6 +6923,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -6743,6 +7018,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-message-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", @@ -6763,6 +7054,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-message-util/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -6845,6 +7152,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-resolve/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -6886,6 +7209,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-runtime": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", @@ -6919,6 +7258,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-runtime/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -6968,6 +7323,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -6985,6 +7356,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-validate": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", @@ -7014,6 +7401,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-watcher": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", @@ -7048,6 +7451,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-watcher/node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -7548,18 +7967,6 @@ "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" } }, - "node_modules/marked-terminal/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", @@ -11740,6 +12147,14 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", diff --git a/package.json b/package.json index 50109d5..143bd06 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "azion", "version": "1.7.0-stage.1", "description": "Azion Packages for Edge Computing.", + "bin": { + "azion": "cli/bin/azion" + }, "scripts": { "prepare": "husky", "compile": "npm run compile --workspaces", @@ -10,7 +13,9 @@ "format": "npm run prettier --workspaces --if-present", "format:check": "npm run prettier:check --workspaces --if-present", "test": "npm run test --workspaces --if-present", - "build": "tsup" + "download:cli": "node cli/scripts/download-cli.mjs", + "postinstall": "npm run download:cli", + "build": "npm run download:cli && npm run compile" }, "keywords": [ "azion", @@ -159,5 +164,9 @@ "./packages/utils/dist/index.d.ts" ] } + }, + "dependencies": { + "chalk": "^5.3.0", + "progress": "^2.0.3" } } From 3adbca24ed89fe890a03475dcc6f76670e8c3119 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 3 Sep 2024 01:14:26 +0000 Subject: [PATCH 13/45] chore(release): 1.7.0-stage.2 [skip ci] ## [1.7.0-stage.2](https://github.com/aziontech/lib/compare/v1.7.0-stage.1...v1.7.0-stage.2) (2024-09-03) ### Features * azion cli binary (#32) ([0670adb](https://github.com/aziontech/lib/commit/0670adb9b4f9086126a8f5387f95e001da3a748b)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 460829c..a498d06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.7.0-stage.2](https://github.com/aziontech/lib/compare/v1.7.0-stage.1...v1.7.0-stage.2) (2024-09-03) + + +### Features + +* azion cli binary (#32) ([0670adb](https://github.com/aziontech/lib/commit/0670adb9b4f9086126a8f5387f95e001da3a748b)) + ## [1.7.0-stage.1](https://github.com/aziontech/lib/compare/v1.6.0...v1.7.0-stage.1) (2024-08-30) diff --git a/package.json b/package.json index 143bd06..a3043c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.7.0-stage.1", + "version": "1.7.0-stage.2", "description": "Azion Packages for Edge Computing.", "bin": { "azion": "cli/bin/azion" From f1aee5e30f0eab8e9f5209c730439af81779309b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 2 Sep 2024 22:27:38 -0300 Subject: [PATCH 14/45] fix: cli bin path (#33) --- cli/scripts/download-cli.mjs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/cli/scripts/download-cli.mjs b/cli/scripts/download-cli.mjs index 4c12af9..3454ca2 100644 --- a/cli/scripts/download-cli.mjs +++ b/cli/scripts/download-cli.mjs @@ -11,8 +11,20 @@ const defaultVersion = '1.37.0'; const version = process.env.AZION_CLI_VERSION || defaultVersion; const baseUrl = `https://github.com/aziontech/azion/releases/download/${version}`; -const currentDir = path.dirname(fileURLToPath(import.meta.url)); -const packageRoot = path.resolve(currentDir, '..'); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Navigate up the directory tree to find the root directory of the installed library +let packageRoot = __dirname; +while (!fs.existsSync(path.join(packageRoot, 'package.json'))) { + const parentDir = path.dirname(packageRoot); + if (parentDir === packageRoot) { + throw new Error('Could not find the root directory of the library'); + } + packageRoot = parentDir; +} + const binDir = path.join(packageRoot, 'bin'); const log = { @@ -174,6 +186,7 @@ async function main() { log.info(`Detected platform: ${chalk.cyan(JSON.stringify(platform))}`); await downloadAndExtract(platform); log.highlight('Installation completed successfully!'); + log.info(`Azion CLI has been installed in: ${chalk.cyan(binDir)}`); process.exit(0); } catch (error) { log.error(`Error during installation: ${error.message}`); From 41bf7208d71a982d709a3c37a76c510f4af9e768 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 3 Sep 2024 01:28:30 +0000 Subject: [PATCH 15/45] chore(release): 1.7.0-stage.3 [skip ci] ## [1.7.0-stage.3](https://github.com/aziontech/lib/compare/v1.7.0-stage.2...v1.7.0-stage.3) (2024-09-03) ### Bug Fixes * cli bin path (#33) ([f1aee5e](https://github.com/aziontech/lib/commit/f1aee5e30f0eab8e9f5209c730439af81779309b)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a498d06..6ba8de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.7.0-stage.3](https://github.com/aziontech/lib/compare/v1.7.0-stage.2...v1.7.0-stage.3) (2024-09-03) + + +### Bug Fixes + +* cli bin path (#33) ([f1aee5e](https://github.com/aziontech/lib/commit/f1aee5e30f0eab8e9f5209c730439af81779309b)) + ## [1.7.0-stage.2](https://github.com/aziontech/lib/compare/v1.7.0-stage.1...v1.7.0-stage.2) (2024-09-03) diff --git a/package.json b/package.json index a3043c8..c593701 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.7.0-stage.2", + "version": "1.7.0-stage.3", "description": "Azion Packages for Edge Computing.", "bin": { "azion": "cli/bin/azion" From c6b5fb4214c012780ba8315acad78d5202eed1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 2 Sep 2024 22:35:09 -0300 Subject: [PATCH 16/45] fix: cli relative path (#34) --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c593701..5c022d1 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "format": "npm run prettier --workspaces --if-present", "format:check": "npm run prettier:check --workspaces --if-present", "test": "npm run test --workspaces --if-present", - "download:cli": "node cli/scripts/download-cli.mjs", + "download:cli": "node ./cli/scripts/download-cli.mjs", "postinstall": "npm run download:cli", "build": "npm run download:cli && npm run compile" }, @@ -72,7 +72,8 @@ "files": [ "README.md", "package.json", - "packages/**/dist/**/*" + "packages/**/dist/**/*", + "cli/**/*" ], "exports": { ".": { From 4f429c419dbf68a2493cbc3e26de2f05e04628eb Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 3 Sep 2024 01:35:58 +0000 Subject: [PATCH 17/45] chore(release): 1.7.0-stage.4 [skip ci] ## [1.7.0-stage.4](https://github.com/aziontech/lib/compare/v1.7.0-stage.3...v1.7.0-stage.4) (2024-09-03) ### Bug Fixes * cli relative path (#34) ([c6b5fb4](https://github.com/aziontech/lib/commit/c6b5fb4214c012780ba8315acad78d5202eed1df)) --- CHANGELOG.md | 7 +++++++ package.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ba8de9..5fcfec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.7.0-stage.4](https://github.com/aziontech/lib/compare/v1.7.0-stage.3...v1.7.0-stage.4) (2024-09-03) + + +### Bug Fixes + +* cli relative path (#34) ([c6b5fb4](https://github.com/aziontech/lib/commit/c6b5fb4214c012780ba8315acad78d5202eed1df)) + ## [1.7.0-stage.3](https://github.com/aziontech/lib/compare/v1.7.0-stage.2...v1.7.0-stage.3) (2024-09-03) diff --git a/package.json b/package.json index 5c022d1..0f4c700 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.7.0-stage.3", + "version": "1.7.0-stage.4", "description": "Azion Packages for Edge Computing.", "bin": { "azion": "cli/bin/azion" @@ -73,7 +73,7 @@ "README.md", "package.json", "packages/**/dist/**/*", - "cli/**/*" + "cli/**/*" ], "exports": { ".": { From 3833208bfd127509616f40aa1177f95a8ec7ff1b Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Mon, 2 Sep 2024 23:01:47 -0300 Subject: [PATCH 18/45] fix: npm bin path --- .gitignore | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 410c2b0..d637f05 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ dist coverage .env .DS_Store -cli/bin \ No newline at end of file +cli/bin/ \ No newline at end of file diff --git a/package.json b/package.json index 0f4c700..3cb0276 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.7.0-stage.4", "description": "Azion Packages for Edge Computing.", "bin": { - "azion": "cli/bin/azion" + "azion": "./cli/bin/azion" }, "scripts": { "prepare": "husky", From ea5bb4f8f6a1cd9d30945d06fdb5357183c3bbf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 2 Sep 2024 23:02:33 -0300 Subject: [PATCH 19/45] fix: npm bin path (#35) --- .gitignore | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 410c2b0..d637f05 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ dist coverage .env .DS_Store -cli/bin \ No newline at end of file +cli/bin/ \ No newline at end of file diff --git a/package.json b/package.json index 0f4c700..3cb0276 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.7.0-stage.4", "description": "Azion Packages for Edge Computing.", "bin": { - "azion": "cli/bin/azion" + "azion": "./cli/bin/azion" }, "scripts": { "prepare": "husky", From d643d14b544547c32287a21389be91959b94e1a3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 3 Sep 2024 02:03:21 +0000 Subject: [PATCH 20/45] chore(release): 1.7.0-stage.5 [skip ci] ## [1.7.0-stage.5](https://github.com/aziontech/lib/compare/v1.7.0-stage.4...v1.7.0-stage.5) (2024-09-03) ### Bug Fixes * npm bin path (#35) ([ea5bb4f](https://github.com/aziontech/lib/commit/ea5bb4f8f6a1cd9d30945d06fdb5357183c3bbf2)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fcfec0..97db476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.7.0-stage.5](https://github.com/aziontech/lib/compare/v1.7.0-stage.4...v1.7.0-stage.5) (2024-09-03) + + +### Bug Fixes + +* npm bin path (#35) ([ea5bb4f](https://github.com/aziontech/lib/commit/ea5bb4f8f6a1cd9d30945d06fdb5357183c3bbf2)) + ## [1.7.0-stage.4](https://github.com/aziontech/lib/compare/v1.7.0-stage.3...v1.7.0-stage.4) (2024-09-03) diff --git a/package.json b/package.json index 3cb0276..096d981 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.7.0-stage.4", + "version": "1.7.0-stage.5", "description": "Azion Packages for Edge Computing.", "bin": { "azion": "./cli/bin/azion" From b43676a324df9f3d470dacabd395450be79144d9 Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Mon, 2 Sep 2024 23:15:29 -0300 Subject: [PATCH 21/45] fix: bin path (again) --- .gitignore | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d637f05..f32edcd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ dist coverage .env .DS_Store -cli/bin/ \ No newline at end of file +bin \ No newline at end of file diff --git a/package.json b/package.json index 096d981..c70bd35 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.7.0-stage.5", "description": "Azion Packages for Edge Computing.", "bin": { - "azion": "./cli/bin/azion" + "azion": "./bin/azion" }, "scripts": { "prepare": "husky", From d6cc8d7812561ae517aa6c88169a69f7bc701fd2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 3 Sep 2024 02:16:21 +0000 Subject: [PATCH 22/45] chore(release): 1.7.0-stage.6 [skip ci] ## [1.7.0-stage.6](https://github.com/aziontech/lib/compare/v1.7.0-stage.5...v1.7.0-stage.6) (2024-09-03) ### Bug Fixes * bin path (again) ([b43676a](https://github.com/aziontech/lib/commit/b43676a324df9f3d470dacabd395450be79144d9)) * npm bin path ([3833208](https://github.com/aziontech/lib/commit/3833208bfd127509616f40aa1177f95a8ec7ff1b)) --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97db476..6aed044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [1.7.0-stage.6](https://github.com/aziontech/lib/compare/v1.7.0-stage.5...v1.7.0-stage.6) (2024-09-03) + + +### Bug Fixes + +* bin path (again) ([b43676a](https://github.com/aziontech/lib/commit/b43676a324df9f3d470dacabd395450be79144d9)) +* npm bin path ([3833208](https://github.com/aziontech/lib/commit/3833208bfd127509616f40aa1177f95a8ec7ff1b)) + ## [1.7.0-stage.5](https://github.com/aziontech/lib/compare/v1.7.0-stage.4...v1.7.0-stage.5) (2024-09-03) diff --git a/package.json b/package.json index c70bd35..b6d6179 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.7.0-stage.5", + "version": "1.7.0-stage.6", "description": "Azion Packages for Edge Computing.", "bin": { "azion": "./bin/azion" From 6588e63ba7fa9f60f0570316ae501a1d21e1fe40 Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Tue, 3 Sep 2024 18:34:49 -0300 Subject: [PATCH 23/45] feat: enable azion stage --- packages/purge/src/services.ts | 6 ++++-- packages/sql/src/services/api/index.ts | 5 ++++- packages/storage/src/services/api/index.ts | 6 ++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/purge/src/services.ts b/packages/purge/src/services.ts index 5fd23b3..a90aabd 100644 --- a/packages/purge/src/services.ts +++ b/packages/purge/src/services.ts @@ -1,7 +1,9 @@ import { ApiPurgeResponse } from './types'; -const BASE_URL = 'https://api.azion.com/v4/edge/purge'; - +const BASE_URL = + process.env.AZION_ENV === 'stage' + ? 'https://stage-api.azion.com/v4/edge/purge' + : 'https://api.azion.com/v4/edge/purge'; /** * Purge URLs from the Azion Edge cache. * diff --git a/packages/sql/src/services/api/index.ts b/packages/sql/src/services/api/index.ts index ed0385d..7652d78 100644 --- a/packages/sql/src/services/api/index.ts +++ b/packages/sql/src/services/api/index.ts @@ -7,7 +7,10 @@ import type { ApiQueryExecutionResponse, } from './types'; -const BASE_URL = 'https://api.azion.com/v4/edge_sql/databases'; +const BASE_URL = + process.env.AZION_ENV === 'stage' + ? 'https://stage-api.azion.com/v4/edge_sql/databases' + : 'https://api.azion.com/v4/edge_sql/databases'; /** * Creates a new Edge Database. diff --git a/packages/storage/src/services/api/index.ts b/packages/storage/src/services/api/index.ts index 6b93e38..4acfc18 100644 --- a/packages/storage/src/services/api/index.ts +++ b/packages/storage/src/services/api/index.ts @@ -10,8 +10,10 @@ import { ApiListObjectsResponse, } from './types'; -const BASE_URL = 'https://api.azion.com/v4/storage/buckets'; - +const BASE_URL = + process.env.AZION_ENV === 'stage' + ? 'https://stage-api.azion.com/v4/edge_sql/databases' + : 'https://api.azion.com/v4/edge_sql/databases'; /** * Retrieves a list of buckets with optional filtering and pagination. * From 5fe4b0d0115760d3bb758a8b823e0e8ec7e94eec Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 3 Sep 2024 21:38:54 +0000 Subject: [PATCH 24/45] chore(release): 1.7.0-stage.7 [skip ci] ## [1.7.0-stage.7](https://github.com/aziontech/lib/compare/v1.7.0-stage.6...v1.7.0-stage.7) (2024-09-03) ### Features * enable azion stage ([6588e63](https://github.com/aziontech/lib/commit/6588e63ba7fa9f60f0570316ae501a1d21e1fe40)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aed044..cb785f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.7.0-stage.7](https://github.com/aziontech/lib/compare/v1.7.0-stage.6...v1.7.0-stage.7) (2024-09-03) + + +### Features + +* enable azion stage ([6588e63](https://github.com/aziontech/lib/commit/6588e63ba7fa9f60f0570316ae501a1d21e1fe40)) + ## [1.7.0-stage.6](https://github.com/aziontech/lib/compare/v1.7.0-stage.5...v1.7.0-stage.6) (2024-09-03) diff --git a/package.json b/package.json index b6d6179..8b634b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.7.0-stage.6", + "version": "1.7.0-stage.7", "description": "Azion Packages for Edge Computing.", "bin": { "azion": "./bin/azion" From dd3e14023735133b5ead3fba89d1f636233d736b Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Tue, 3 Sep 2024 18:54:08 -0300 Subject: [PATCH 25/45] fix: storage endpoint --- packages/storage/src/services/api/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/storage/src/services/api/index.ts b/packages/storage/src/services/api/index.ts index 4acfc18..1cd8785 100644 --- a/packages/storage/src/services/api/index.ts +++ b/packages/storage/src/services/api/index.ts @@ -12,8 +12,9 @@ import { const BASE_URL = process.env.AZION_ENV === 'stage' - ? 'https://stage-api.azion.com/v4/edge_sql/databases' - : 'https://api.azion.com/v4/edge_sql/databases'; + ? 'https://stage-api.azion.com/v4/storage/buckets' + : 'https://api.azion.com/v4/storage/buckets'; + /** * Retrieves a list of buckets with optional filtering and pagination. * From c995bbbd8168cc9138370baf9849e20267e067a9 Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Tue, 3 Sep 2024 19:27:00 -0300 Subject: [PATCH 26/45] fix: storage params --- packages/storage/src/index.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts index 35a656a..cb23ab1 100644 --- a/packages/storage/src/index.ts +++ b/packages/storage/src/index.ts @@ -446,7 +446,7 @@ const getBucketsWrapper = ({ }: { params?: AzionBucketCollectionParams; options?: AzionClientOptions; -}): Promise => +} = {}): Promise => getBucketsMethod(resolveToken(), params, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -684,8 +684,10 @@ const client: CreateAzionStorageClient = ( const debugValue = resolveDebug(config?.options?.debug); const client: AzionStorageClient = { - getBuckets: (params?: { params?: AzionBucketCollectionParams }): Promise => - getBucketsMethod(tokenValue, params?.params, { ...config, debug: debugValue }), + getBuckets: ( + params: { params?: AzionBucketCollectionParams; options?: AzionClientOptions } = {}, + ): Promise => + getBucketsMethod(tokenValue, params.params, { ...config, ...params.options, debug: debugValue }), createBucket: ({ name, edge_access }: { name: string; edge_access: string }): Promise => createBucketMethod(tokenValue, name, edge_access, { ...config, debug: debugValue }), updateBucket: ({ name, edge_access }: { name: string; edge_access: string }): Promise => From ae09397eda45a45b9c837435778d00b492d848d4 Mon Sep 17 00:00:00 2001 From: jotanarciso Date: Tue, 3 Sep 2024 19:35:38 -0300 Subject: [PATCH 27/45] docs: fix query/execute demo --- packages/sql/README.MD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sql/README.MD b/packages/sql/README.MD index 1124009..46a20a7 100644 --- a/packages/sql/README.MD +++ b/packages/sql/README.MD @@ -419,7 +419,7 @@ Executes a query on a specific database. **Example:** ```javascript -const { data, error } = await sqlClient.useQuery('my-db', ['SELECT * FROM users']); +const { data, error } = await sqlClient.query('my-db', ['SELECT * FROM users']); if (data) { console.log(`Query executed. Rows returned: ${data.rows.length}`); } else { @@ -444,7 +444,7 @@ Executes a set of SQL statements on a specific database. **Example:** ```javascript -const { data, error } = await sqlClient.useExecute('my-db', ['INSERT INTO users (name) VALUES ("John")']); +const { data, error } = await sqlClient.execute('my-db', ['INSERT INTO users (name) VALUES ("John")']); if (data) { console.log('Executed with success'); } else { From 3d2fb9459fcb6edb8383b243def442fb6f272ea9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 3 Sep 2024 22:35:52 +0000 Subject: [PATCH 28/45] chore(release): 1.7.0-stage.8 [skip ci] ## [1.7.0-stage.8](https://github.com/aziontech/lib/compare/v1.7.0-stage.7...v1.7.0-stage.8) (2024-09-03) ### Bug Fixes * storage endpoint ([dd3e140](https://github.com/aziontech/lib/commit/dd3e14023735133b5ead3fba89d1f636233d736b)) * storage params ([c995bbb](https://github.com/aziontech/lib/commit/c995bbbd8168cc9138370baf9849e20267e067a9)) --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb785f7..fd7dc1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [1.7.0-stage.8](https://github.com/aziontech/lib/compare/v1.7.0-stage.7...v1.7.0-stage.8) (2024-09-03) + + +### Bug Fixes + +* storage endpoint ([dd3e140](https://github.com/aziontech/lib/commit/dd3e14023735133b5ead3fba89d1f636233d736b)) +* storage params ([c995bbb](https://github.com/aziontech/lib/commit/c995bbbd8168cc9138370baf9849e20267e067a9)) + ## [1.7.0-stage.7](https://github.com/aziontech/lib/compare/v1.7.0-stage.6...v1.7.0-stage.7) (2024-09-03) diff --git a/package.json b/package.json index 8b634b4..51e386d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.7.0-stage.7", + "version": "1.7.0-stage.8", "description": "Azion Packages for Edge Computing.", "bin": { "azion": "./bin/azion" From 088bbd359a9f33de73352a35db59601dee053285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Filho?= Date: Thu, 5 Sep 2024 17:01:07 -0300 Subject: [PATCH 29/45] feat: updating function returns (#36) * feat: updated the return of azion/storage functions Updated the return of the azion/storage functions to the standard { data, error }. * feat: updated the return of azion/sql functions * refactor: update name types of azion/storage * feat: updated the return of azion/purge functions * refactor: adjusting estate types in methods in action/sql * chore: adding the state in deleteDatabase azion/sql * fix: api types azion/storage * fix: updated to include the 'items' property in the error handling process azion/purge --- README.md | 56 +- packages/client/README.MD | 61 ++- packages/client/src/types.ts | 20 +- packages/purge/README.md | 74 +-- packages/purge/src/index.test.ts | 34 +- packages/purge/src/index.ts | 65 ++- .../{services.ts => services/api/index.ts} | 52 +- packages/purge/src/services/api/types.ts | 10 + packages/purge/src/types.ts | 33 +- packages/sql/README.MD | 104 ++-- packages/sql/src/index.test.ts | 68 +-- packages/sql/src/index.ts | 182 +++---- packages/sql/src/services/api/index.ts | 123 +++-- packages/sql/src/services/api/types.ts | 27 +- packages/sql/src/services/index.ts | 61 +-- packages/sql/src/services/runtime/index.ts | 8 +- packages/sql/src/types.ts | 67 +-- packages/sql/src/utils/mappers/to-object.ts | 22 +- packages/storage/README.md | 426 ++++++++++++--- packages/storage/src/index.test.ts | 104 ++-- packages/storage/src/index.ts | 498 +++++++++++++----- packages/storage/src/services/api/index.ts | 81 ++- packages/storage/src/services/api/types.d.ts | 56 +- .../storage/src/services/runtime/index.ts | 94 +++- packages/storage/src/types.ts | 77 ++- packages/storage/src/utils/index.ts | 24 +- 26 files changed, 1580 insertions(+), 847 deletions(-) rename packages/purge/src/{services.ts => services/api/index.ts} (59%) create mode 100644 packages/purge/src/services/api/types.ts diff --git a/README.md b/README.md index 5b39b5d..c7fe0f9 100644 --- a/README.md +++ b/README.md @@ -54,15 +54,15 @@ const client = createClient({ token: 'your-api-token', debug: true }); const newBucket = await client.storage.createBucket('my-new-bucket', 'public'); console.log(`Bucket created with name: ${newBucket.name}`); -const allBuckets = await client.storage.getBuckets(); -console.log(`Retrieved ${allBuckets.length} buckets`); +const { data: allBuckets } = await client.storage.getBuckets(); +console.log(`Retrieved ${allBuckets.count} buckets`); // SQL -const newDatabase = await client.sql.createDatabase('my-new-db'); -console.log(`Database created with ID: ${newDatabase.data.id}`); +const { data: newDatabase } = await client.sql.createDatabase('my-new-db'); +console.log(`Database created with ID: ${newDatabase.id}`); -const allDatabases = await client.sql.getDatabases(); -console.log(`Retrieved ${allDatabases.data.length} databases`); +const { data: allDatabases } = await client.sql.getDatabases(); +console.log(`Retrieved ${allDatabases.count} databases`); // Purge const purgeResult = await client.purge.purgeURL(['http://example.com/image.jpg']); @@ -74,7 +74,7 @@ console.log(`Purge successful: ${purgeResult.items}`); ```typescript import { createClient } from 'azion'; import type { AzionClient, Bucket, Purge } from 'azion/client'; -import type { AzionDatabaseResponse, AzionDatabaseQueryResponse } from 'azion/sql'; +import type { AzionDatabaseResponse, AzionDatabaseQueryResponse, AzionBuckets } from 'azion/sql'; const client: AzionClient = createClient({ token: 'your-api-token', debug: true }); @@ -82,15 +82,15 @@ const client: AzionClient = createClient({ token: 'your-api-token', debug: true const newBucket: Bucket | null = await client.storage.createBucket('my-new-bucket', 'public'); console.log(`Bucket created with name: ${newBucket.name}`); -const allBuckets: Bucket[] | null = await client.storage.getBuckets(); -console.log(`Retrieved ${allBuckets.length} buckets`); +const { data: allBuckets }: AzionStorageResponse = await client.storage.getBuckets(); +console.log(`Retrieved ${allBuckets.count} buckets`); // SQL -const newDatabase: AzionDatabaseResponse = await client.sql.createDatabase('my-new-db'); -console.log(`Database created with ID: ${newDatabase.data.id}`); +const { data: newDatabase }: AzionDatabaseResponse = await client.sql.createDatabase('my-new-db'); +console.log(`Database created with ID: ${newDatabase.id}`); -const allDatabases: AzionDatabaseResponse = await client.sql.getDatabases(); -console.log(`Retrieved ${allDatabases.data.length} databases`); +const { data: allDatabases }: AzionDatabaseResponse = await client.sql.getDatabases(); +console.log(`Retrieved ${allDatabases.count} databases`); // Purge const purgeResult: Purge | null = await client.purge.purgeURL(['http://example.com/image.jpg']); @@ -110,14 +110,14 @@ import { createClient } from 'azion/storage'; const client = createClient({ token: 'your-api-token', debug: true }); -const newBucket = await client.createBucket('my-new-bucket', 'public'); -if (newBucket) { - console.log(`Bucket created with name: ${newBucket.name}`); +const { data, error } = await client.createBucket('my-new-bucket', 'public'); +if (data) { + console.log(`Bucket created with name: ${data.name}`); } -const allBuckets = await client.getBuckets(); +const { data: allBuckets } = await client.getBuckets(); if (allBuckets) { - console.log(`Retrieved ${allBuckets.length} buckets`); + console.log(`Retrieved ${allBuckets.count} buckets`); } ``` @@ -125,18 +125,18 @@ if (allBuckets) { ```typescript import { createClient } from 'azion/storage'; -import { StorageClient, Bucket } from 'azion/storage/types'; +import type { AzionStorageClient, AzionStorageResponse, AzionBucket, AzionBuckets } from 'azion/storage'; -const client: StorageClient = createClient({ token: 'your-api-token', debug: true }); +const client: AzionStorageClient = createClient({ token: 'your-api-token', debug: true }); -const newBucket: Bucket | null = await client.createBucket('my-new-bucket', 'public'); -if (newBucket) { - console.log(`Bucket created with name: ${newBucket.name}`); +const { data, error }: AzionStorageResponse = await client.createBucket('my-new-bucket', 'public'); +if (data) { + console.log(`Bucket created with name: ${data.name}`); } -const allBuckets: Bucket[] | null = await client.getBuckets(); +const { data: allBuckets }: AzionStorageResponse = await client.getBuckets(); if (allBuckets) { - console.log(`Retrieved ${allBuckets.length} buckets`); + console.log(`Retrieved ${allBuckets.count} buckets`); } ``` @@ -179,9 +179,9 @@ if (data) { console.log(`Database created with ID: ${data.id}`); } -const allDatabases: AzionDatabaseResponse = await client.getDatabases(); -if (allDatabases.data) { - console.log(`Retrieved ${allDatabases.data.length} databases`); +const { data: allDatabases }: AzionDatabaseResponse = await client.getDatabases(); +if (allDatabases) { + console.log(`Retrieved ${allDatabases.count} databases`); } ``` diff --git a/packages/client/README.MD b/packages/client/README.MD index c95df6a..e056170 100644 --- a/packages/client/README.MD +++ b/packages/client/README.MD @@ -69,17 +69,17 @@ import { createClient } from 'azion'; const client = createClient({ token: 'your-api-token', debug: true }); -const newBucket = await client.storage.createBucket('my-new-bucket', 'public'); +const { data: newBucket } = await client.storage.createBucket('my-new-bucket', 'public'); if (newBucket) { console.log(`Bucket created with name: ${newBucket.name}`); } -const allBuckets = await client.storage.getBuckets(); +const { data: allBuckets } = await client.storage.getBuckets(); if (allBuckets) { - console.log(`Retrieved ${allBuckets.length} buckets`); + console.log(`Retrieved ${allBuckets.count} buckets`); } -const deletedBucket = await client.storage.deleteBucket('my-bucket'); +const { data: deletedBucket } = await client.storage.deleteBucket('my-bucket'); if (deletedBucket) { console.log(`Bucket deleted with name: ${deletedBucket.name}`); } @@ -89,21 +89,25 @@ if (deletedBucket) { ```typescript import { createClient } from 'azion'; -import { AzionClient, Bucket } from 'azion/storage/types'; +import { AzionClient, AzionStorageResponse, AzionBucket, AzionDeletedBucket, AzionBuckets } from 'azion/storage'; const client: AzionClient = createClient({ token: 'your-api-token', debug: true }); -const newBucket: Bucket | null = await client.storage.createBucket('my-new-bucket', 'public'); +const { data: newBucket }: AzionStorageResponse = await client.storage.createBucket( + 'my-new-bucket', + 'public', +); if (newBucket) { console.log(`Bucket created with name: ${newBucket.name}`); } -const allBuckets: Bucket[] | null = await client.storage.getBuckets(); +const { data: allBuckets }: AzionStorageResponse = await client.storage.getBuckets(); if (allBuckets) { - console.log(`Retrieved ${allBuckets.length} buckets`); + console.log(`Retrieved ${allBuckets.count} buckets`); } -const deletedBucket: Bucket | null = await client.storage.deleteBucket('my-bucket'); +const { data: deletedBucket }: AzionStorageResponse = + await client.storage.deleteBucket('my-bucket'); if (deletedBucket) { console.log(`Bucket deleted with name: ${deletedBucket.name}`); } @@ -123,9 +127,9 @@ if (newDatabase) { console.log(`AzionDatabase created with ID: ${newDatabase.id}`); } -const allDatabases = await client.sql.getDatabases(); -if (allDatabases?.data) { - console.log(`Retrieved ${allDatabases?.data?.length} databases`); +const { data: allDatabases } = await client.sql.getDatabases(); +if (allDatabases) { + console.log(`Retrieved ${allDatabases?.count} databases`); } const queryResult = await client.sql.query('SELECT * FROM users'); @@ -143,19 +147,19 @@ import type { AzionDatabaseResponse, AzionDatabaseQueryResponse } from 'azion/sq const client: AzionClient = createClient({ token: 'your-api-token', { debug: true } }); -const { data: newDatabase, error }: AzionDatabaseResponse = await client.sql.createDatabase('my-new-db'); +const { data: newDatabase, error }: AzionDatabaseResponse = await client.sql.createDatabase('my-new-db'); if (newDatabase) { console.log(`AzionDatabase created with ID: ${newDatabase.id}`); } -const allDatabases: AzionDatabaseResponse = await client.sql.getDatabases(); -if (allDatabases?.data) { - console.log(`Retrieved ${allDatabases?.data?.length} databases`); +const { data: allDatabases }: AzionDatabaseResponse = await client.sql.getDatabases(); +if (allDatabases) { + console.log(`Retrieved ${allDatabases?.count} databases`); } -const queryResult: AzionDatabaseQueryResponse = await client.sql.query(['SELECT * FROM users']); +const queryResult: AzionDatabaseResponse = await client.sql.query(['SELECT * FROM users']); if (queryResult?.data) { - console.log(`Query executed. Rows returned: ${queryResult?.data?.length}`); + console.log(`Query executed. Rows returned: ${queryResult?.data?.results.length}`); } ``` @@ -168,17 +172,17 @@ import { createClient } from 'azion'; const client = createClient({ token: 'your-api-token', { debug: true } }); -const purgeResult = await client.purge.purgeURL(['http://example.com/image.jpg']); +const { data: purgeResult } = await client.purge.purgeURL(['http://example.com/image.jpg']); if (purgeResult) { console.log(`Purge successful: ${purgeResult.items}`); } -const cacheKeyResult = await client.purge.purgeCacheKey(['my-cache-key-1', 'my-cache-key-2']); +const { data: cacheKeyResult } = await client.purge.purgeCacheKey(['my-cache-key-1', 'my-cache-key-2']); if (cacheKeyResult) { console.log(`Cache key purge successful: ${cacheKeyResult.items}`); } -const wildcardResult = await client.purge.purgeWildCard(['http://example.com/*']); +const { data: wildcardResult } = await client.purge.purgeWildCard(['http://example.com/*']); if (wildcardResult) { console.log(`Wildcard purge successful: ${wildcardResult.items}`); } @@ -188,21 +192,28 @@ if (wildcardResult) { ```typescript import { createClient } from 'azion'; -import { AzionClient, Purge } from 'azion/purge/types'; +import { AzionClient, AzionPurgeResponse, AzionPurge } from 'azion/purge/types'; const client: AzionClient = createClient({ token: 'your-api-token', debug: true }); -const purgeResult: Purge | null = await client.purge.purgeURL(['http://example.com/image.jpg']); +const { data: purgeResult }: AzionPurgeResponse = await client.purge.purgeURL([ + 'http://example.com/image.jpg', +]); if (purgeResult) { console.log(`Purge successful: ${purgeResult.items}`); } -const cacheKeyResult: Purge | null = await client.purge.purgeCacheKey(['my-cache-key-1', 'my-cache-key-2']); +const { data: cacheKeyResult }: AzionPurgeResponse = await client.purge.purgeCacheKey([ + 'my-cache-key-1', + 'my-cache-key-2', +]); if (cacheKeyResult) { console.log(`Cache key purge successful: ${cacheKeyResult.items}`); } -const wildcardResult: Purge | null = await client.purge.purgeWildCard(['http://example.com/*']); +const { data: wildcardResult }: AzionPurgeResponse = await client.purge.purgeWildCard([ + 'http://example.com/*', +]); if (wildcardResult) { console.log(`Wildcard purge successful: ${wildcardResult.items}`); } diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index 0accdb9..276566c 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -16,26 +16,26 @@ export interface AzionClient { * * @example * // Get all buckets - * const allBuckets = await client.storage.getBuckets({ params: { page: 1, page_size: 10 } }); + * const { data: allBuckets } = await client.storage.getBuckets({ params: { page: 1, page_size: 10 } }); * * @example * // Get a specific bucket and perform operations - * const bucket = await client.storage.getBucket({ name: 'my-bucket' }); + * const { data: bucket } = await client.storage.getBucket({ name: 'my-bucket' }); * if (bucket) { * // Upload a new object - * const newObject = await bucket.createObject({ key: 'example.txt', content: 'Hello, World!' }); + * const { data: newObject } = await bucket.createObject({ key: 'example.txt', content: 'Hello, World!' }); * * // Get all objects in the bucket - * const objects = await bucket.getObjects({ params: { page: 1, page_size: 10 } }); + * const { data: objectsResult } = await bucket.getObjects({ params: { page: 1, page_size: 10 } }); * * // Get a specific object - * const object = await bucket.getObjectByKey({ key: 'example.txt' }); + * const { data: object, error } = await bucket.getObjectByKey({ key: 'example.txt' }); * * // Update an object - * const updatedObject = await bucket.updateObject({ key: 'example.txt', content: 'Updated content' }); + * const { data: updatedObject } = await bucket.updateObject({ key: 'example.txt', content: 'Updated content' }); * * // Delete an object - * const deletedObject = await bucket.deleteObject({ key: 'example.txt' }); + * const { data: deletedObject } = await bucket.deleteObject({ key: 'example.txt' }); * } * * @example @@ -51,7 +51,7 @@ export interface AzionClient { * * @example * // Create a new database - * const newDatabase = await client.sql.createDatabase('my-new-db'); + * const { data: newDatabase } = await client.sql.createDatabase('my-new-db'); * * @example * // Get all databases @@ -59,7 +59,7 @@ export interface AzionClient { * * @example * // Get a specific database and perform operations - * const { data:db, error } = await client.sql.getDatabase('my-db'); + * const { data: db, error } = await client.sql.getDatabase('my-db'); * if (db) { * // Execute a query * const queryResult = await db.query(['SELECT * FROM users WHERE id = ?', 1]); @@ -71,7 +71,7 @@ export interface AzionClient { * ], ['John Doe', 'john@example.com', 1]); * * // List tables in the database - * const tables = await db.listTables(); + * const tables = await db.getTables(); * } * * @example diff --git a/packages/purge/README.md b/packages/purge/README.md index 74e041c..bbfb65d 100644 --- a/packages/purge/README.md +++ b/packages/purge/README.md @@ -82,25 +82,25 @@ You can create a client instance with specific configurations. import { purgeURL } from 'azion/purge'; const url = ['http://www.domain.com/path/image.jpg']; -const response = await purgeURL(url, { debug: true }); +const { data: response, error } = await purgeURL(url, { debug: true }); if (response) { console.log('Purge successful:', response); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } ``` **TypeScript:** ```typescript -import { purgeURL, AzionPurge } from 'azion/purge'; +import { purgeURL, AzionPurgeResponse, AzionPurge } from 'azion/purge'; const url: string[] = ['http://www.domain.com/path/image.jpg']; -const response: AzionPurge | null = await purgeURL(url, { debug: true }); +const { data: response, error }: AzionPurgeResponse = await purgeURL(url, { debug: true }); if (response) { console.log('Purge successful:', response); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } ``` @@ -112,25 +112,25 @@ if (response) { import { purgeCacheKey } from 'azion/purge'; const cacheKey = ['http://www.domain.com/path/image.jpg']; -const response = await purgeCacheKey(cacheKey, { debug: true }); +const { data: response, error } = await purgeCacheKey(cacheKey, { debug: true }); if (response) { console.log('Purge successful:', response); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } ``` **TypeScript:** ```typescript -import { purgeCacheKey, AzionPurge } from 'azion/purge'; +import { purgeCacheKey, AzionPurge, AzionPurgeResponse } from 'azion/purge'; const cacheKey: string[] = ['http://www.domain.com/path/image.jpg']; -const response: AzionPurge | null = await purgeCacheKey(cacheKey, { debug: true }); +const { data: response, error }: AzionPurgeResponse = await purgeCacheKey(cacheKey, { debug: true }); if (response) { console.log('Purge successful:', response); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } ``` @@ -142,25 +142,25 @@ if (response) { import { purgeWildCard } from 'azion/purge'; const wildcard = ['http://www.domain.com/path/image.jpg*']; -const response = await purgeWildCard(wildcard, { debug: true }); +const { data: response, error } = await purgeWildCard(wildcard, { debug: true }); if (response) { console.log('Purge successful:', response); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } ``` **TypeScript:** ```typescript -import { purgeWildCard, AzionPurge } from 'azion/purge'; +import { purgeWildCard, AzionPurge, AzionPurgeResponse } from 'azion/purge'; const wildcard: string[] = ['http://www.domain.com/path/image.jpg*']; -const response: AzionPurge | null = await purgeWildCard(wildcard, { debug: true }); +const { data: response, error }: AzionPurgeResponse = await purgeWildCard(wildcard, { debug: true }); if (response) { console.log('Purge successful:', response); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } ``` @@ -173,54 +173,60 @@ import { createClient } from 'azion/purge'; const client = createClient({ token: 'your-api-token', options: { debug: true } }); -const purgeURLResponse = await client.purgeURL(['http://www.domain.com/path/image.jpg']); +const { data: purgeURLResponse } = await client.purgeURL(['http://www.domain.com/path/image.jpg']); if (purgeURLResponse) { console.log('Purge successful:', purgeURLResponse); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } -const purgeCacheKeyResponse = await client.purgeCacheKey(['http://www.domain.com/path/image.jpg']); +const { data: purgeCacheKeyResponse } = await client.purgeCacheKey(['http://www.domain.com/path/image.jpg']); if (purgeCacheKeyResponse) { console.log('Purge successful:', purgeCacheKeyResponse); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } -const purgeWildCardResponse = await client.purgeWildCard(['http://www.domain.com/path/image.jpg*']); +const { data: purgeWildCardResponse } = await client.purgeWildCard(['http://www.domain.com/path/image.jpg*']); if (purgeWildCardResponse) { console.log('Purge successful:', purgeWildCardResponse); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } ``` **TypeScript:** ```typescript -import { createClient, AzionPurge, AzionPurgeClient } from 'azion/purge'; +import { createClient, AzionPurge, AzionPurgeClient, AzionPurgeResponse } from 'azion/purge'; const client: AzionPurgeClient = createClient({ token: 'your-api-token', options: { debug: true } }); -const purgeURLResponse: AzionPurge | null = await client.purgeURL(['http://www.domain.com/path/image.jpg']); +const { data: purgeURLResponse, error }: AzionPurgeResponse = await client.purgeURL([ + 'http://www.domain.com/path/image.jpg', +]); if (purgeURLResponse) { console.log('Purge successful:', purgeURLResponse); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } -const purgeCacheKeyResponse: AzionPurge | null = await client.purgeCacheKey(['http://www.domain.com/path/image.jpg']); +const { data: purgeCacheKeyResponse, error }: AzionPurgeResponse = await client.purgeCacheKey([ + 'http://www.domain.com/path/image.jpg', +]); if (purgeCacheKeyResponse) { console.log('Purge successful:', purgeCacheKeyResponse); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } -const purgeWildCardResponse: AzionPurge | null = await client.purgeWildCard(['http://www.domain.com/path/image.jpg*']); +const { data: purgeWildCardResponse, error }: AzionPurgeResponse = await client.purgeWildCard([ + 'http://www.domain.com/path/image.jpg*', +]); if (purgeWildCardResponse) { console.log('Purge successful:', purgeWildCardResponse); } else { - console.error('Purge failed'); + console.error('Purge failed', error); } ``` @@ -237,7 +243,7 @@ Purge a URL from the Azion Edge cache. **Returns:** -- `Promise` - The purge response or null if the purge failed. +- `Promise>` - The purge response or error if the purge failed. ### `purgeCacheKey` @@ -250,7 +256,7 @@ Purge a Cache Key from the Azion Edge cache. **Returns:** -- `Promise` - The purge response or null if the purge failed. +- `Promise>` - The purge response or error if the purge failed. ### `purgeWildCard` @@ -263,7 +269,7 @@ Purge using a wildcard expression from the Azion Edge cache. **Returns:** -- `Promise` - The purge response or null if the purge failed. +- `Promise>` - The purge response or error if the purge failed. ### `createClient` @@ -290,9 +296,9 @@ Configuration options for the Purge client. An object with methods to interact with Purge. -- `purgeURL: (urls: string[], options?: AzionClientOptions) => Promise` -- `purgeCacheKey: (cacheKeys: string[], options?: AzionClientOptions) => Promise` -- `purgeWildCard: (wildcards: string[], options?: AzionClientOptions) => Promise` +- `purgeURL: (urls: string[], options?: AzionClientOptions) => Promise>` +- `purgeCacheKey: (cacheKeys: string[], options?: AzionClientOptions) => Promise>` +- `purgeWildCard: (wildcards: string[], options?: AzionClientOptions) => Promise>` ### `Purge` diff --git a/packages/purge/src/index.test.ts b/packages/purge/src/index.test.ts index 1e901cc..5a60ea7 100644 --- a/packages/purge/src/index.test.ts +++ b/packages/purge/src/index.test.ts @@ -1,9 +1,9 @@ import { createClient, purgeCacheKey, purgeURL, purgeWildCard } from '../src/index'; import { AzionPurgeClient } from '../src/types'; -import * as services from '../src/services'; +import * as services from '../src/services/api/index'; -jest.mock('../src/services'); +jest.mock('../src/services/api/index'); describe('Purge Module', () => { const mockToken = 'mock-token'; @@ -37,15 +37,17 @@ describe('Purge Module', () => { (services.postPurgeURL as jest.Mock).mockResolvedValue(mockResponse); const result = await purgeURL(['http://example.com'], { debug: mockDebug }); - expect(result).toEqual({ state: 'executed', items: ['http://example.com'] }); + expect(result.data).toEqual({ state: 'executed', items: ['http://example.com'] }); expect(services.postPurgeURL).toHaveBeenCalledWith(mockToken, ['http://example.com'], mockDebug); }); - it('should return null on failure', async () => { - (services.postPurgeURL as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.postPurgeURL as jest.Mock).mockResolvedValue({ + error: { message: 'Invalid Tokne', operation: 'purgeURL' }, + }); const result = await purgeURL(['http://example.com'], { debug: mockDebug }); - expect(result).toBeNull(); + expect(result).toEqual({ error: { message: 'Invalid Tokne', operation: 'purgeURL' } }); }); }); @@ -55,15 +57,17 @@ describe('Purge Module', () => { (services.postPurgeCacheKey as jest.Mock).mockResolvedValue(mockResponse); const result = await purgeCacheKey(['cache-key-1'], { debug: true }); - expect(result).toEqual({ state: 'executed', items: ['cache-key-1'] }); + expect(result.data).toEqual({ state: 'executed', items: ['cache-key-1'] }); expect(services.postPurgeCacheKey).toHaveBeenCalledWith(mockToken, ['cache-key-1'], mockDebug); }); - it('should return null on failure', async () => { - (services.postPurgeCacheKey as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.postPurgeCacheKey as jest.Mock).mockResolvedValue({ + error: { message: 'Invalid Token', operation: 'purge cache key' }, + }); const result = await purgeCacheKey(['cache-key-1'], { debug: mockDebug }); - expect(result).toBeNull(); + expect(result).toEqual({ error: { message: 'Invalid Token', operation: 'purge cache key' } }); }); }); @@ -73,15 +77,17 @@ describe('Purge Module', () => { (services.postPurgeWildcard as jest.Mock).mockResolvedValue(mockResponse); const result = await purgeWildCard(['http://example.com/*'], { debug: mockDebug }); - expect(result).toEqual({ state: 'executed', items: ['http://example.com/*'] }); + expect(result.data).toEqual({ state: 'executed', items: ['http://example.com/*'] }); expect(services.postPurgeWildcard).toHaveBeenCalledWith(mockToken, ['http://example.com/*'], mockDebug); }); - it('should return null on failure', async () => { - (services.postPurgeWildcard as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.postPurgeWildcard as jest.Mock).mockResolvedValue({ + error: { message: 'Invalid Token', operation: 'purge wildcard' }, + }); const result = await purgeWildCard(['http://example.com/*'], { debug: mockDebug }); - expect(result).toBeNull(); + expect(result).toEqual({ error: { message: 'Invalid Token', operation: 'purge wildcard' } }); }); }); diff --git a/packages/purge/src/index.ts b/packages/purge/src/index.ts index b20b2e8..d552605 100644 --- a/packages/purge/src/index.ts +++ b/packages/purge/src/index.ts @@ -1,5 +1,5 @@ -import { postPurgeCacheKey, postPurgeURL, postPurgeWildcard } from './services'; -import { AzionClientOptions, AzionPurge, AzionPurgeClient, CreateAzionPurgeClient } from './types'; +import { postPurgeCacheKey, postPurgeURL, postPurgeWildcard } from './services/api/index'; +import { AzionClientOptions, AzionPurge, AzionPurgeClient, AzionPurgeResponse, CreateAzionPurgeClient } from './types'; const envDebugFlag = process.env.AZION_DEBUG && process.env.AZION_DEBUG === 'true'; const resolveToken = (token?: string) => token ?? process.env.AZION_TOKEN ?? ''; @@ -9,36 +9,47 @@ const purgeURLMethod = async ( token: string, url: string[], options?: AzionClientOptions, -): Promise => { +): Promise> => { const apiResponse = await postPurgeURL(resolveToken(token), url, resolveDebug(options?.debug)); - if (apiResponse) { - return { items: apiResponse.data.items, state: apiResponse.state }; + if (apiResponse?.data && apiResponse.state) { + return { data: { items: apiResponse.data.items, state: apiResponse.state } }; } - return null; + return { + error: apiResponse.error, + }; }; const purgeCacheKeyMethod = async ( token: string, cacheKey: string[], options?: AzionClientOptions, -): Promise => { +): Promise> => { const apiResponse = await postPurgeCacheKey(resolveToken(token), cacheKey, resolveDebug(options?.debug)); - if (apiResponse) { - return { items: apiResponse.data.items, state: apiResponse.state }; + if (apiResponse?.data && apiResponse.state) { + return { + data: { + items: apiResponse.data.items, + state: apiResponse.state, + }, + }; } - return null; + return { + error: apiResponse.error, + }; }; const purgeWildCardMethod = async ( token: string, wildcard: string[], options?: AzionClientOptions, -): Promise => { +): Promise> => { const apiResponse = await postPurgeWildcard(resolveToken(token), wildcard, resolveDebug(options?.debug)); - if (apiResponse) { - return { items: apiResponse.data.items, state: apiResponse.state }; + if (apiResponse?.data && apiResponse.state) { + return { data: { items: apiResponse.data.items, state: apiResponse.state } }; } - return null; + return { + error: apiResponse.error, + }; }; /** @@ -46,7 +57,7 @@ const purgeWildCardMethod = async ( * * @param {string[]} url - URLs to purge. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The purge response or null if the purge failed. + * @returns {Promise>} The purge response or null if the purge failed. * * @example * const response = await purgeURL(['http://www.domain.com/path/image.jpg'], { debug: true }); @@ -56,7 +67,7 @@ const purgeWildCardMethod = async ( * console.error('Purge failed'); * } */ -const purgeURLWrapper = (url: string[], options?: AzionClientOptions): Promise => +const purgeURLWrapper = (url: string[], options?: AzionClientOptions): Promise> => purgeURLMethod(resolveToken(), url, options); /** @@ -64,7 +75,7 @@ const purgeURLWrapper = (url: string[], options?: AzionClientOptions): Promise} The purge response or null if the purge failed. + * @returns {Promise>} The purge response or null if the purge failed. * * @example * const response = await purgeCacheKey(['http://www.domain.com/path/image.jpg'], { debug: true }); @@ -74,15 +85,17 @@ const purgeURLWrapper = (url: string[], options?: AzionClientOptions): Promise => - purgeCacheKeyMethod(resolveToken(), cacheKey, options); +const purgeCacheKeyWrapper = ( + cacheKey: string[], + options?: AzionClientOptions, +): Promise> => purgeCacheKeyMethod(resolveToken(), cacheKey, options); /** * Purge using a wildcard expression from the Azion Edge cache. * * @param {string[]} wildcard - Wildcard expressions to purge. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The purge response or null if the purge failed. + * @returns {Promise>} The purge response or null if the purge failed. * * @example * const response = await purgeWildCard(['http://www.domain.com/path/image.jpg*'], { debug: true }); @@ -92,8 +105,10 @@ const purgeCacheKeyWrapper = (cacheKey: string[], options?: AzionClientOptions): * console.error('Purge failed'); * } */ -const purgeWildCardWrapper = (wildcard: string[], options?: AzionClientOptions): Promise => - purgeWildCardMethod(resolveToken(), wildcard, options); +const purgeWildCardWrapper = ( + wildcard: string[], + options?: AzionClientOptions, +): Promise> => purgeWildCardMethod(resolveToken(), wildcard, options); /** * Creates a Purge client with methods to interact with Azion Edge Purge. @@ -119,11 +134,11 @@ const client: CreateAzionPurgeClient = ( const tokenValue = resolveToken(config?.token); const client: AzionPurgeClient = { - purgeURL: (url: string[], options?: AzionClientOptions): Promise => + purgeURL: (url: string[], options?: AzionClientOptions): Promise> => purgeURLMethod(tokenValue, url, options), - purgeCacheKey: (cacheKey: string[], options?: AzionClientOptions): Promise => + purgeCacheKey: (cacheKey: string[], options?: AzionClientOptions): Promise> => purgeCacheKeyMethod(tokenValue, cacheKey, options), - purgeWildCard: (wildcard: string[], options?: AzionClientOptions): Promise => + purgeWildCard: (wildcard: string[], options?: AzionClientOptions): Promise> => purgeWildCardMethod(tokenValue, wildcard, options), }; diff --git a/packages/purge/src/services.ts b/packages/purge/src/services/api/index.ts similarity index 59% rename from packages/purge/src/services.ts rename to packages/purge/src/services/api/index.ts index a90aabd..fbaed52 100644 --- a/packages/purge/src/services.ts +++ b/packages/purge/src/services/api/index.ts @@ -4,15 +4,31 @@ const BASE_URL = process.env.AZION_ENV === 'stage' ? 'https://stage-api.azion.com/v4/edge/purge' : 'https://api.azion.com/v4/edge/purge'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const handleApiError = (fields: string[], data: any, operation: string) => { + let error = { message: 'Error unknown', operation: operation }; + fields.forEach((field: string) => { + if (data[field]) { + const message = Array.isArray(data[field]) ? data[field].join(', ') : data[field]; + error = { + message: message, + operation: operation, + }; + } + }); + return error; +}; + /** * Purge URLs from the Azion Edge cache. * * @param {string} token - Authentication token for Azion API. * @param {string[]} urls - URLs to purge. * @param {boolean} [debug] - Enable debug mode for detailed logging. - * @returns {Promise} The purge response or null if the purge failed. + * @returns {Promise} The purge response or error message */ -const postPurgeURL = async (token: string, urls: string[], debug?: boolean): Promise => { +const postPurgeURL = async (token: string, urls: string[], debug?: boolean): Promise => { return postPurge(`${BASE_URL}/url`, token, urls, debug); }; @@ -22,9 +38,9 @@ const postPurgeURL = async (token: string, urls: string[], debug?: boolean): Pro * @param {string} token - Authentication token for Azion API. * @param {string[]} urls - Cache keys to purge. * @param {boolean} [debug] - Enable debug mode for detailed logging. - * @returns {Promise} The purge response or null if the purge failed. + * @returns {Promise} The purge response or error message */ -const postPurgeCacheKey = async (token: string, urls: string[], debug?: boolean): Promise => { +const postPurgeCacheKey = async (token: string, urls: string[], debug?: boolean): Promise => { return postPurge(`${BASE_URL}/cachekey`, token, urls, debug); }; @@ -34,9 +50,9 @@ const postPurgeCacheKey = async (token: string, urls: string[], debug?: boolean) * @param {string} token - Authentication token for Azion API. * @param {string[]} urls - Wildcard expressions to purge. * @param {boolean} [debug] - Enable debug mode for detailed logging. - * @returns {Promise} The purge response or null if the purge failed. + * @returns {Promise} The purge response or error message */ -const postPurgeWildcard = async (token: string, urls: string[], debug?: boolean): Promise => { +const postPurgeWildcard = async (token: string, urls: string[], debug?: boolean): Promise => { return postPurge(`${BASE_URL}/wildcard`, token, urls, debug); }; @@ -47,14 +63,9 @@ const postPurgeWildcard = async (token: string, urls: string[], debug?: boolean) * @param {string} token - Authentication token for Azion API. * @param {string[]} urls - Items to purge. * @param {boolean} [debug] - Enable debug mode for detailed logging. - * @returns {Promise} The purge response or null if the purge failed. + * @returns {Promise} The purge response or error if the purge failed. */ -const postPurge = async ( - url: string, - token: string, - urls: string[], - debug?: boolean, -): Promise => { +const postPurge = async (url: string, token: string, urls: string[], debug?: boolean): Promise => { try { const response = await fetch(url, { method: 'POST', @@ -66,12 +77,19 @@ const postPurge = async ( credentials: 'include', body: JSON.stringify({ items: urls, layer: 'edge_cache' }), }); - const data = await response.json(); - if (debug) console.log('Response:', data); - return data; + const result = await response.json(); + if (!result.data) { + if (debug) console.log('Response Error:', result); + result.error = handleApiError(['detail', 'error', 'items'], result, 'post purge'); + return { + error: result.error ?? JSON.stringify(result), + }; + } + if (debug) console.log('Response:', result); + return result; } catch (error) { if (debug) console.error('Error purging:', error); - return null; + throw error; } }; diff --git a/packages/purge/src/services/api/types.ts b/packages/purge/src/services/api/types.ts new file mode 100644 index 0000000..5a22bee --- /dev/null +++ b/packages/purge/src/services/api/types.ts @@ -0,0 +1,10 @@ +export interface ApiPurgeResponse { + state?: 'executed' | 'pending'; + data?: { + items: string[]; + }; + error?: { + message: string; + operation: string; + }; +} diff --git a/packages/purge/src/types.ts b/packages/purge/src/types.ts index e364b8d..ab55672 100644 --- a/packages/purge/src/types.ts +++ b/packages/purge/src/types.ts @@ -1,7 +1,8 @@ -export interface ApiPurgeResponse { - state: 'executed' | 'pending'; - data: { - items: string[]; +export interface AzionPurgeResponse { + data?: T; + error?: { + message: string; + operation: string; }; } @@ -16,49 +17,49 @@ export interface AzionPurgeClient { * * @param {string[]} url - URLs to purge. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The purge response or null if the purge failed. + * @returns {Promise>} The purge response or error if the purge failed. * * @example - * const response = await purgeClient.purgeURL(['http://www.domain.com/path/image.jpg'], { debug: true }); + * const { data: response, error } = await purgeClient.purgeURL(['http://www.domain.com/path/image.jpg'], { debug: true }); * if (response) { * console.log('Purge successful:', response); * } else { - * console.error('Purge failed'); + * console.error('Purge failed', error); * } */ - purgeURL: (urls: string[]) => Promise; + purgeURL: (urls: string[]) => Promise>; /** * Purge a Cache Key from the Azion Edge cache. * * @param {string[]} cacheKey - Cache Keys to purge. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The purge response or null if the purge failed. + * @returns {Promise>} The purge response or error if the purge failed. * * @example - * const response = await purgeClient.purgeCacheKey(['http://www.domain.com/path/image.jpg'], { debug: true }); + * const { data: response, error } = await purgeClient.purgeCacheKey(['http://www.domain.com/path/image.jpg'], { debug: true }); * if (response) { * console.log('Purge successful:', response); * } else { - * console.error('Purge failed'); + * console.error('Purge failed', error); * } */ - purgeCacheKey: (cacheKeys: string[]) => Promise; + purgeCacheKey: (cacheKeys: string[]) => Promise>; /** * Purge using a wildcard expression from the Azion Edge cache. * * @param {string[]} wildcard - Wildcard expressions to purge. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The purge response or null if the purge failed. + * @returns {Promise>} The purge response or error if the purge failed. * * @example - * const response = await purgeClient.purgeWildCard(['http://www.domain.com/path/image.jpg*'], { debug: true }); + * const { data: response, error } = await purgeClient.purgeWildCard(['http://www.domain.com/path/image.jpg*'], { debug: true }); * if (response) { * console.log('Purge successful:', response); * } else { - * console.error('Purge failed'); + * console.error('Purge failed', error); * } */ - purgeWildCard: (wildcards: string[]) => Promise; + purgeWildCard: (wildcards: string[]) => Promise>; } /** diff --git a/packages/sql/README.MD b/packages/sql/README.MD index 46a20a7..bde4b28 100644 --- a/packages/sql/README.MD +++ b/packages/sql/README.MD @@ -112,7 +112,7 @@ if (data) { import { createDatabase, AzionDatabase } from 'azion/sql'; import type { AzionDatabaseResponse, AzionDatabase } from 'azion/sql'; -const { data, error }: AzionDatabaseResponse = await createDatabase('my-new-database', { debug: true }); +const { data, error }: AzionDatabaseResponse = await createDatabase('my-new-database', { debug: true }); if (data) { const database: AzionDatabase = data; console.log(`Database created with ID: ${database.id}`); @@ -140,9 +140,9 @@ if (data) { ```typescript import { deleteDatabase } from 'azion/sql'; -import type { AzionDatabaseResponse } from 'azion/sql'; +import type { AzionDatabaseResponse, AzionDatabaseDeleteResponse } from 'azion/sql'; -const { data, error }: AzionDatabaseResponse = await deleteDatabase(123, { debug: true }); +const { data, error }: AzionDatabaseResponse = await deleteDatabase(123, { debug: true }); if (data) { console.log(`Database ${data.id} deleted successfully`); } else { @@ -171,7 +171,7 @@ if (data) { import { getDatabase } from 'azion/sql'; import type { AzionDatabaseResponse, AzionDatabase } from 'azion/sql'; -const { data, error }: AzionDatabaseResponse = await getDatabase('my-db', { debug: true }); +const { data, error }: AzionDatabaseResponse = await getDatabase('my-db', { debug: true }); if (data) { const database: AzionDatabase = data; console.log(`Retrieved database: ${database.id}`); @@ -199,12 +199,14 @@ if (data) { ```typescript import { getDatabases } from 'azion/sql'; -import type { AzionDatabaseResponse, AzionDatabase } from 'azion/sql'; +import type { AzionDatabaseResponse, AzionDatabaseCollections } from 'azion/sql'; -const { data, error }: AzionDatabaseResponse = await getDatabases({ page: 1, page_size: 10 }, { debug: true }); -if (data) { - const databases: AzionDatabase[] = data; - console.log(`Retrieved ${databases.length} databases`); +const { data: allDatabases, error }: AzionDatabaseResponse = await getDatabases( + { page: 1, page_size: 10 }, + { debug: true }, +); +if (allDatabases) { + console.log(`Retrieved ${allDatabases.count} databases`); } else { console.error('Failed to retrieve databases', error); } @@ -228,13 +230,19 @@ if (result) { **TypeScript:** ```typescript -import { useQuery, AzionDatabaseQueryResponse } from 'azion/sql'; +import { useQuery, AzionDatabaseQueryResponse, AzionDatabaseResponse } from 'azion/sql'; -const result: AzionDatabaseQueryResponse | null = await useQuery('my-db', ['SELECT * FROM users'], { debug: true }); +const { data: result, error }: AzionDatabaseResponse = await useQuery( + 'my-db', + ['SELECT * FROM users'], + { + debug: true, + }, +); if (result) { console.log(`Query executed. Rows returned: ${result.rows.length}`); } else { - console.error('Query execution failed'); + console.error('Query execution failed', error); } ``` @@ -277,9 +285,9 @@ if (result?.state === 'executed') { **JavaScript:** ```javascript -import { listTables } from 'azion/sql'; +import { getTables } from 'azion/sql'; -const { data, error } = await listTables('my-db', { debug: true }); +const { data, error } = await getTables('my-db', { debug: true }); if (data) { console.log(data); @@ -291,10 +299,10 @@ if (data) { **TypeScript:** ```typescript -import { listTables } from 'azion/sql'; -import type { AzionDatabaseQueryResponse } from 'azion/sql'; +import { getTables } from 'azion/sql'; +import type { AzionDatabaseResponse, AzionDatabaseQueryResponse } from 'azion/sql'; -const { data, error }: AzionDatabaseQueryResponse = await listTables('my-db', { debug: true }); +const { data, error }: AzionDatabaseResponse = await getTables('my-db', { debug: true }); if (data) { console.log(data); @@ -316,7 +324,7 @@ Creates a new database. **Returns:** -- `Promise` - The created database object or error. +- `Promise>` - The created database object or error. **Example:** @@ -340,7 +348,7 @@ Deletes a database by its ID. **Returns:** -- `Promise` - Object confirming deletion or error. +- `Promise>` - Object confirming deletion or error. **Example:** @@ -364,7 +372,7 @@ Retrieves a database by its Name. **Returns:** -- `Promise` - The retrieved database object or error. +- `Promise>` - The retrieved database object or error. **Example:** @@ -388,15 +396,15 @@ Retrieves a list of databases with optional filtering and pagination. **Returns:** -- `Promise` - Array of database objects or error. +- `Promise>` - Array of database objects or error. **Example:** ```javascript -const { data, error } = await sqlClient.getDatabases({ page: 1, page_size: 10, search: 'test' }); -if (data) { - console.log(`Retrieved ${data.length} databases`); - data.forEach((db) => console.log(`- ${db.name} (ID: ${db.id})`)); +const { data: allDatabases, error } = await sqlClient.getDatabases({ page: 1, page_size: 10, search: 'test' }); +if (allDatabases) { + console.log(`Retrieved ${allDatabases.count} databases`); + allDatabases.results.forEach((db) => console.log(`- ${db.name} (ID: ${db.id})`)); } else { console.error('Failed to retrieve databases', error); } @@ -414,7 +422,7 @@ Executes a query on a specific database. **Returns:** -- `Promise` - Query result object or error. +- `Promise>` - Query result object or error. **Example:** @@ -439,7 +447,7 @@ Executes a set of SQL statements on a specific database. **Returns:** -- `Promise` - Execution result object or error. +- `Promise>` - Execution result object or error. **Example:** @@ -479,12 +487,12 @@ Configuration options for the SQL client. An object with methods to interact with SQL databases. -- `createDatabase: (name: string) => Promise` -- `deleteDatabase: (id: number) => Promise` -- `getDatabase: (name: string) => Promise` -- `getDatabases: (params?: AzionDatabaseCollectionOptions) => Promise` -- `useQuery: (name: string, statements: string[], options?: AzionClientOptions) => Promise` -- `useExecute: (name: string, statements: string[], options?: AzionClientOptions) => Promise` +- `createDatabase: (name: string) => Promise>` +- `deleteDatabase: (id: number) => Promise>` +- `getDatabase: (name: string) => Promise>` +- `getDatabases: (params?: AzionDatabaseCollectionOptions) => Promise>` +- `useQuery: (name: string, statements: string[], options?: AzionClientOptions) => Promise>` +- `useExecute: (name: string, statements: string[], options?: AzionClientOptions) => Promise>` ### `AzionDatabase` @@ -497,15 +505,15 @@ The database object. - `createdAt: string` - `updatedAt: string` - `deletedAt: string | null` -- `query: (statements: string[], options?: AzionClientOptions) => Promise` -- `execute: (statements: string[], options?: AzionClientOptions) => Promise` -- `listTables: (options?: AzionClientOptions) => Promise` +- `query: (statements: string[], options?: AzionClientOptions) => Promise>` +- `execute: (statements: string[], options?: AzionClientOptions) => Promise>` +- `getTables: (options?: AzionClientOptions) => Promise>` -### `AzionDatabaseResponse` +### `AzionDatabaseResponse` The response object from a database operation. -- `data?: AzionDatabase | Pick` +- `data?: T` - `error?: { message: string, operation: string }` ### `QueryResult` @@ -536,8 +544,7 @@ Optional parameters for filtering and pagination. The response object from a query execution. - `state: 'executed' | 'pending' | 'executed-runtime' | 'failed'` -- `data: QueryResult[] | NonSelectQueryResult` -- `error?: { message: string, operation: string }` +- `data: QueryResult[]` - `toObject?: () => JsonObjectQueryExecutionResponse` ### `AzionDatabaseExecutionResponse` @@ -545,24 +552,9 @@ The response object from a query execution. The response object from a query execution. - `state: 'executed' | 'pending' | 'executed-runtime' | 'failed'` -- `data: QueryResult[] | NonSelectQueryResult` -- `error?: { message: string, operation: string }` +- `data: QueryResult[]` - `toObject?: () => JsonObjectQueryExecutionResponse` -### `NonSelectQueryResult` - -The result object for non-select queries. - -- `info?: AzionQueryExecutionInfo` - -### `AzionQueryExecutionInfo` - -Information about the query execution. - -- `rowsRead?: number` -- `rowsWritten?: number` -- `durationMs?: number` - ### `AzionQueryExecutionParams` Parameters for query execution. diff --git a/packages/sql/src/index.test.ts b/packages/sql/src/index.test.ts index 1df7970..35dcf49 100644 --- a/packages/sql/src/index.test.ts +++ b/packages/sql/src/index.test.ts @@ -4,13 +4,13 @@ import { deleteDatabase, getDatabase, getDatabases, - listTables, + getTables, useExecute, useQuery, } from '../src/index'; import * as servicesApi from '../src/services/api/index'; import * as services from '../src/services/index'; -import { AzionDatabase, AzionDatabaseResponse, AzionSQLClient } from './types'; +import { AzionSQLClient } from './types'; jest.mock('../src/services/api/index'); @@ -49,15 +49,17 @@ describe('SQL Module', () => { it('should successfully create a database', async () => { const mockResponse = { data: { id: 1, name: 'test-db' } }; (servicesApi.postEdgeDatabase as jest.Mock).mockResolvedValue(mockResponse); - const result: AzionDatabaseResponse = await createDatabase('test-db', { debug: mockDebug }); + const result = await createDatabase('test-db', { debug: mockDebug }); expect(result.data).toEqual(expect.objectContaining({ id: 1, name: 'test-db' })); expect(servicesApi.postEdgeDatabase).toHaveBeenCalledWith(mockToken, 'test-db', true); }); it('should return error on failure', async () => { (servicesApi.postEdgeDatabase as jest.Mock).mockResolvedValue({ - state: 'failed', - error: { detail: 'Database already exists' }, + error: { + message: 'Database already exists', + operation: 'create database', + }, }); const result = await createDatabase('test-db', { debug: mockDebug }); expect(result).toEqual({ @@ -71,15 +73,15 @@ describe('SQL Module', () => { jest.spyOn(console, 'log').mockImplementation(); }); it('should successfully delete a database', async () => { - (servicesApi.deleteEdgeDatabase as jest.Mock).mockResolvedValue({ state: 'success' }); + (servicesApi.deleteEdgeDatabase as jest.Mock).mockResolvedValue({ state: 'pending', data: { id: 1 } }); const result = await deleteDatabase(1, { debug: mockDebug }); - expect(result).toEqual({ data: { id: 1 } }); + expect(result).toEqual({ data: { id: 1, state: 'pending' } }); expect(servicesApi.deleteEdgeDatabase).toHaveBeenCalledWith(mockToken, 1, true); }); it('should return error on failure', async () => { (servicesApi.deleteEdgeDatabase as jest.Mock).mockResolvedValue({ - error: { detail: 'Database not found' }, + error: { message: 'Failed to delete database', operation: 'delete database' }, }); const result = await deleteDatabase(1, { debug: mockDebug }); expect(result).toEqual({ @@ -96,7 +98,7 @@ describe('SQL Module', () => { const mockResponse = { results: [{ id: 1, name: 'test-db' }] }; (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue(mockResponse); - const result = (await getDatabase('test-db', { debug: mockDebug })) as AzionDatabaseResponse; + const result = await getDatabase('test-db', { debug: mockDebug }); expect(result.data).toEqual(expect.objectContaining({ id: 1, name: 'test-db' })); expect(servicesApi.getEdgeDatabases).toHaveBeenCalledWith(mockToken, { search: 'test-db' }, true); }); @@ -124,15 +126,17 @@ describe('SQL Module', () => { (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue(mockResponse); const { data } = await getDatabases({ page: 1, page_size: 10 }, { debug: mockDebug }); - expect(data).toHaveLength(2); - expect((data as AzionDatabase[])[0]).toEqual(expect.objectContaining({ id: 1, name: 'test-db-1' })); + expect(data?.databases).toHaveLength(2); + expect(data!.databases![0]).toEqual(expect.objectContaining({ id: 1, name: 'test-db-1' })); expect(servicesApi.getEdgeDatabases).toHaveBeenCalledWith(mockToken, { page: 1, page_size: 10 }, true); }); it('should return error if retrieval fails', async () => { - (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue({ state: 'failed' }); + (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue({ + error: { message: 'Failed to retrieve databases', operation: 'get databases' }, + }); - const result = (await getDatabases({ page: 1, page_size: 10 }, { debug: mockDebug })) as AzionDatabaseResponse; + const result = await getDatabases({ page: 1, page_size: 10 }, { debug: mockDebug }); expect(result).toEqual({ error: { message: 'Failed to retrieve databases', operation: 'get databases' } }); }); }); @@ -161,10 +165,9 @@ describe('SQL Module', () => { (servicesApi.postQueryEdgeDatabase as jest.Mock).mockResolvedValue(mockResponse); const result = await useQuery('test-db', ['SELECT * FROM test'], { debug: mockDebug }); - expect(result).toEqual( + expect(result.data).toEqual( expect.objectContaining({ - state: 'executed', - data: [{ columns: ['id', 'name'], rows: [[1, 'test']], info: undefined, statement: 'SELECT' }], + results: [{ columns: ['id', 'name'], rows: [[1, 'test']], statement: 'SELECT' }], }), ); expect(servicesApi.postQueryEdgeDatabase).toHaveBeenCalledWith(mockToken, 1, ['SELECT * FROM test'], true); @@ -193,14 +196,19 @@ describe('SQL Module', () => { }; (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue(mockResponseDatabases); const mockResponse = { - state: 'executed', data: [{ results: { columns: [], rows: [] } }], }; (servicesApi.postQueryEdgeDatabase as jest.Mock).mockResolvedValue(mockResponse); + const result = await useExecute('test-db', ["INSERT INTO users (id, name) VALUES (1, 'John Doe')"], { + debug: mockDebug, + }); + + expect(result.data).toEqual( + expect.objectContaining({ + results: [{ statement: 'INSERT' }], + }), + ); - expect( - await useExecute('test-db', ["INSERT INTO users (id, name) VALUES (1, 'John Doe')"], { debug: mockDebug }), - ).toEqual(expect.objectContaining({ state: 'executed' })); expect(servicesApi.postQueryEdgeDatabase).toHaveBeenCalledWith( mockToken, 1, @@ -219,25 +227,23 @@ describe('SQL Module', () => { (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue(mockResponseDatabases); await expect(useExecute('test-db', ['SELECT * FROM test'], { debug: mockDebug })).resolves.toEqual({ - state: 'failed', error: { message: 'Only write statements are allowed', operation: 'execute database' }, }); }); it('should return error if query execution fails', async () => { (servicesApi.postQueryEdgeDatabase as jest.Mock).mockResolvedValue({ - state: 'failed', + error: { message: 'Error executing query', operation: 'executing query' }, }); const result = await useQuery('test-db', ['SELECT * FROM test'], { debug: mockDebug }); expect(result).toEqual({ - state: 'failed', error: { message: 'Error executing query', operation: 'executing query' }, }); }); it('should successfully execute useQuery with apiQuery called', async () => { - const spyApiQuery = jest.spyOn(services, 'apiQuery').mockResolvedValue({ state: 'executed', data: [] }); + const spyApiQuery = jest.spyOn(services, 'apiQuery').mockResolvedValue({ data: { toObject: () => null } }); await useQuery('test-db', ['SELECT * FROM test'], { debug: mockDebug }); @@ -252,7 +258,9 @@ describe('SQL Module', () => { it('should successfully execute useQuery with runtimeQuery called', async () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any (globalThis as any).Azion = { Sql: {} }; - const spyRuntimeQuery = jest.spyOn(services, 'runtimeQuery').mockResolvedValue({ state: 'executed', data: [] }); + const spyRuntimeQuery = jest + .spyOn(services, 'runtimeQuery') + .mockResolvedValue({ data: { toObject: () => null } }); await useQuery('test-db', ['SELECT * FROM test'], { debug: mockDebug }); @@ -265,7 +273,7 @@ describe('SQL Module', () => { }); }); - describe('listTables', () => { + describe('getTables', () => { it('should successfully list tables', async () => { const mockResponseDatabases = { results: [ @@ -275,7 +283,6 @@ describe('SQL Module', () => { }; (servicesApi.getEdgeDatabases as jest.Mock).mockResolvedValue(mockResponseDatabases); const mockResponse = { - state: 'executed', data: [ { results: { @@ -287,11 +294,10 @@ describe('SQL Module', () => { }; (servicesApi.postQueryEdgeDatabase as jest.Mock).mockResolvedValue(mockResponse); - const result = await listTables('test-db', { debug: mockDebug }); - expect(result).toEqual( + const result = await getTables('test-db', { debug: mockDebug }); + expect(result.data).toEqual( expect.objectContaining({ - state: 'executed', - data: [ + results: [ { statement: 'PRAGMA', columns: ['schema', 'name', 'type', 'ncol', 'wr', 'strict'], diff --git a/packages/sql/src/index.ts b/packages/sql/src/index.ts index 7e42966..05be823 100644 --- a/packages/sql/src/index.ts +++ b/packages/sql/src/index.ts @@ -4,7 +4,10 @@ import { apiQuery, runtimeQuery } from './services/index'; import { getAzionSql } from './services/runtime/index'; import type { AzionClientOptions, + AzionDatabase, AzionDatabaseCollectionOptions, + AzionDatabaseCollections, + AzionDatabaseDeleteResponse, AzionDatabaseQueryResponse, AzionDatabaseResponse, AzionSQLClient, @@ -26,35 +29,33 @@ const createDatabaseMethod = async ( token: string, name: string, options?: AzionClientOptions, -): Promise => { - const { data, error } = await postEdgeDatabase(resolveToken(token), name, resolveDebug(options?.debug)); - if (data) { - return Promise.resolve({ +): Promise> => { + const apiResponse = await postEdgeDatabase(resolveToken(token), name, resolveDebug(options?.debug)); + if (apiResponse.data) { + return { data: { - ...data, + state: apiResponse.state, + ...apiResponse.data, query: (statements: string[]) => - queryDatabaseMethod(resolveToken(token), data.name, statements, { + queryDatabaseMethod(resolveToken(token), name, statements, { ...options, debug: resolveDebug(options?.debug), }), execute: (statements: string[], options?: AzionClientOptions) => - executeDatabaseMethod(resolveToken(token), data.name, statements, { + executeDatabaseMethod(resolveToken(token), name, statements, { ...options, debug: resolveDebug(options?.debug), }), - listTables: (options?: AzionClientOptions) => - listTablesWrapper(data.name, { + getTables: (options?: AzionClientOptions) => + listTablesWrapper(name, { ...options, debug: resolveDebug(options?.debug), }), }, - } as AzionDatabaseResponse); + } as AzionDatabaseResponse; } return { - error: { - message: error?.detail ?? 'Failed to create database', - operation: 'create database', - }, + error: apiResponse.error, }; }; @@ -69,21 +70,19 @@ const deleteDatabaseMethod = async ( token: string, id: number, options?: AzionClientOptions, -): Promise => { +): Promise> => { const apiResponse = await deleteEdgeDatabase(resolveToken(token), id, resolveDebug(options?.debug)); - if (apiResponse?.state) { - return Promise.resolve({ + if (apiResponse?.data) { + return { data: { - id, + state: apiResponse.state ?? 'executed', + id: apiResponse.data.id, }, - }); + }; } - return Promise.resolve({ - error: { - message: 'Failed to delete database', - operation: 'delete database', - }, - }); + return { + error: apiResponse.error, + }; }; /** @@ -97,34 +96,34 @@ const getDatabaseMethod = async ( token: string, name: string, options?: AzionClientOptions, -): Promise => { +): Promise> => { if (!name || name === '') { - return Promise.resolve({ + return { error: { message: 'Database name is required', operation: 'get database', }, - }); + }; } const databaseResponse = await getEdgeDatabases(resolveToken(token), { search: name }, resolveDebug(options?.debug)); if (!databaseResponse?.results || databaseResponse?.results?.length === 0) { - return Promise.resolve({ + return { error: { message: `Database with name '${name}' not found`, operation: 'get database', }, - }); + }; } const databaseResult = databaseResponse?.results[0]; if (!databaseResult || databaseResult.id === undefined || databaseResult.name !== name) { - return Promise.resolve({ + return { error: { message: `Database with name '${name}' not found`, operation: 'get database', }, - }); + }; } - return Promise.resolve({ + return { data: { ...databaseResult, query: (statements: string[]) => @@ -137,13 +136,13 @@ const getDatabaseMethod = async ( ...options, debug: resolveDebug(options?.debug), }), - listTables: (options?: AzionClientOptions) => + getTables: (options?: AzionClientOptions) => listTablesWrapper(databaseResult.name, { ...options, debug: resolveDebug(options?.debug), }), }, - } as AzionDatabaseResponse); + } as AzionDatabaseResponse; }; /** @@ -157,39 +156,42 @@ const getDatabasesMethod = async ( token: string, params?: AzionDatabaseCollectionOptions, options?: AzionClientOptions, -): Promise => { +): Promise> => { const apiResponse = await getEdgeDatabases(resolveToken(token), params, resolveDebug(options?.debug)); if (apiResponse?.results && apiResponse.results.length > 0) { const databases = apiResponse.results.map((db: ApiDatabaseResponse) => { return { ...db, - query: (statements: string[]): Promise => + query: (statements: string[]): Promise> => queryDatabaseMethod(resolveToken(token), db.name, statements, { ...options, debug: resolveDebug(options?.debug), }), - execute: (statements: string[], options?: AzionClientOptions): Promise => + execute: ( + statements: string[], + options?: AzionClientOptions, + ): Promise> => executeDatabaseMethod(resolveToken(token), db.name, statements, { ...options, debug: resolveDebug(options?.debug), }), - listTables: (options?: AzionClientOptions): Promise => + getTables: (options?: AzionClientOptions): Promise> => listTablesWrapper(db.name, { ...options, debug: resolveDebug(options?.debug), }), }; }); - return Promise.resolve({ - data: databases as AzionDatabaseResponse['data'], - }); + return { + data: { + count: apiResponse.count, + databases, + }, + }; } - return Promise.resolve({ - error: { - message: apiResponse?.detail ?? 'Failed to retrieve databases', - operation: 'get databases', - }, - }); + return { + error: apiResponse.error, + }; }; /** @@ -205,27 +207,25 @@ const queryDatabaseMethod = async ( name: string, statements: string[], options?: AzionClientOptions, -): Promise => { +): Promise> => { if (!name || name === '') { - return Promise.resolve({ - state: 'failed', + return { error: { message: 'Database name is required', operation: 'query database', }, - }); + }; } if (options?.debug) { console.log(`Executing statements on database ${name}: ${statements}`); } if (!Array.isArray(statements) || statements.length === 0) { - return Promise.resolve({ - state: 'failed', + return { error: { message: 'No statements to execute. Please provide at least one statement. e.g ["SELECT * FROM users"]', operation: 'query database', }, - }); + }; } const isStatement = statements.some((statement) => ['SELECT', 'PRAGMA'].some((keyword) => statement.trim().toUpperCase().startsWith(keyword)), @@ -253,28 +253,26 @@ const executeDatabaseMethod = async ( name: string, statements: string[], options?: AzionClientOptions, -): Promise => { +): Promise> => { if (options?.debug) { console.log(`Executing statements on database ${name}: ${statements}`); } if (!name || name === '') { - return Promise.resolve({ - state: 'failed', + return { error: { message: 'Database name is required', operation: 'execute database', }, - }); + }; } if (!Array.isArray(statements) || statements.length === 0) { - return Promise.resolve({ - state: 'failed', + return { error: { message: 'No statements to execute. Please provide at least one statement. e.g ["INSERT INTO users (name) VALUES (\'John\')"]', operation: 'execute database', }, - }); + }; } const isWriteStatement = statements.some((statement) => ['INSERT', 'UPDATE', 'DELETE'].some((keyword) => statement.trim().toUpperCase().startsWith(keyword)), @@ -283,26 +281,23 @@ const executeDatabaseMethod = async ( ['CREATE', 'ALTER', 'DROP', 'TRUNCATE'].some((keyword) => statement.trim().toUpperCase().startsWith(keyword)), ); if (!isAdminStatement && !isWriteStatement) { - return Promise.resolve({ - state: 'failed', + return { error: { message: 'Only write statements are allowed', operation: 'execute database', }, - }); + }; } if (isAdminStatement && options?.force === false) { - return Promise.resolve({ - state: 'failed', + return { error: { message: 'To admin statements, you need to set the force option to true', operation: 'execute database', }, - }); + }; } const resultQuery = await apiQuery(token, name, statements, options); return { - state: resultQuery.state, data: resultQuery.data, }; }; @@ -312,7 +307,7 @@ const executeDatabaseMethod = async ( * * @param {string} name - Name of the database to create. * @param {AzionClientOptions} [options] - Optional parameters for the deletion. - * @returns {Promise} The created database object or error if creation failed. + * @returns {Promise>} The created database object or error if creation failed. * * @example * const { data, error } = await createDatabase('my-new-database', { debug: true }); @@ -322,7 +317,10 @@ const executeDatabaseMethod = async ( * console.error(`Failed to create database: ${error.message}`); * } */ -const createDatabaseWrapper = async (name: string, options?: AzionClientOptions): Promise => +const createDatabaseWrapper = async ( + name: string, + options?: AzionClientOptions, +): Promise> => await createDatabaseMethod(resolveToken(), name, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -330,7 +328,7 @@ const createDatabaseWrapper = async (name: string, options?: AzionClientOptions) * * @param {number} id - ID of the database to delete. * @param {AzionClientOptions} [options] - Optional parameters for the deletion. - * @returns {Promise} Object confirming deletion or error if deletion failed. + * @returns {Promise>} Object confirming deletion or error if deletion failed. * * @example * const { data, error } = await deleteDatabase(123, { debug: true }); @@ -340,7 +338,10 @@ const createDatabaseWrapper = async (name: string, options?: AzionClientOptions) * console.error(`Failed to delete database: ${error.message}`); * */ -const deleteDatabaseWrapper = (id: number, options?: AzionClientOptions): Promise => +const deleteDatabaseWrapper = ( + id: number, + options?: AzionClientOptions, +): Promise> => deleteDatabaseMethod(resolveToken(), id, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -348,7 +349,7 @@ const deleteDatabaseWrapper = (id: number, options?: AzionClientOptions): Promis * * @param {string} name - Name of the database to retrieve. * @param {AzionClientOptions} [options] - Optional parameters for the deletion. - * @returns {Promise} The retrieved database object or error if not found. + * @returns {Promise>} The retrieved database object or error if not found. * * @example * const { data, error } = await getDatabase('my-db', { debug: true }); @@ -358,7 +359,10 @@ const deleteDatabaseWrapper = (id: number, options?: AzionClientOptions): Promis * console.error(`Failed to retrieve database: ${error.message}`); * } */ -const getDatabaseWrapper = async (name: string, options?: AzionClientOptions): Promise => +const getDatabaseWrapper = async ( + name: string, + options?: AzionClientOptions, +): Promise> => getDatabaseMethod(resolveToken(), name, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -366,13 +370,13 @@ const getDatabaseWrapper = async (name: string, options?: AzionClientOptions): P * * @param {Partial} [params] - Optional parameters for filtering and pagination. * @param {AzionClientOptions} [options] - Optional parameters for the deletion. - * @returns {Promise} Array of database objects or error if retrieval failed. + * @returns {Promise>} Array of database objects or error if retrieval failed. * * @example - * const { data, error } = await getDatabases({ page: 1, page_size: 10 }, { debug: true }); - * if (data) { - * console.log(`Retrieved ${data.length} databases`); - * data.forEach(db => console.log(`- ${db.name} (ID: ${db.id})`)); + * const { data: allDatabases, error } = await getDatabases({ page: 1, page_size: 10 }, { debug: true }); + * if (allDatabases) { + * console.log(`Retrieved ${allDatabases.count} databases`); + * allDatabases.results.forEach(db => console.log(`- ${db.name} (ID: ${db.id})`)); * } else { * console.error('Failed to retrieve databases', error); * } @@ -381,7 +385,7 @@ const getDatabaseWrapper = async (name: string, options?: AzionClientOptions): P const getDatabasesWrapper = ( params?: Partial, options?: AzionClientOptions, -): Promise => +): Promise> => getDatabasesMethod(resolveToken(), params, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -393,7 +397,7 @@ const getDatabasesWrapper = ( const listTablesWrapper = async ( databaseName: string, options?: AzionClientOptions, -): Promise => { +): Promise> => { return queryDatabaseMethod(resolveToken(), databaseName, ['PRAGMA table_list'], { ...options, debug: resolveDebug(options?.debug), @@ -420,7 +424,7 @@ const useQuery = ( name: string, statements: string[], options?: AzionClientOptions, -): Promise => +): Promise> => queryDatabaseMethod(resolveToken(), name, statements, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -443,7 +447,7 @@ const useExecute = async ( name: string, statements: string[], options?: AzionClientOptions, -): Promise => +): Promise> => executeDatabaseMethod(resolveToken(), name, statements, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -471,13 +475,13 @@ const client: CreateAzionSQLClient = ( const debugValue = resolveDebug(config?.options?.debug); const client: AzionSQLClient = { - createDatabase: (name: string): Promise => + createDatabase: (name: string): Promise> => createDatabaseMethod(tokenValue, name, { ...config, debug: debugValue }), - deleteDatabase: (id: number): Promise => + deleteDatabase: (id: number): Promise> => deleteDatabaseMethod(tokenValue, id, { ...config, debug: debugValue }), - getDatabase: (name: string): Promise => + getDatabase: (name: string): Promise> => getDatabaseMethod(tokenValue, name, { ...config, debug: debugValue }), - getDatabases: (params?: AzionDatabaseCollectionOptions): Promise => + getDatabases: (params?: AzionDatabaseCollectionOptions): Promise> => getDatabasesMethod(tokenValue, params, { ...config, debug: debugValue }), } as const; @@ -489,7 +493,7 @@ export { deleteDatabaseWrapper as deleteDatabase, getDatabaseWrapper as getDatabase, getDatabasesWrapper as getDatabases, - listTablesWrapper as listTables, + listTablesWrapper as getTables, useExecute, useQuery, }; diff --git a/packages/sql/src/services/api/index.ts b/packages/sql/src/services/api/index.ts index 7652d78..19cfb05 100644 --- a/packages/sql/src/services/api/index.ts +++ b/packages/sql/src/services/api/index.ts @@ -12,6 +12,21 @@ const BASE_URL = ? 'https://stage-api.azion.com/v4/edge_sql/databases' : 'https://api.azion.com/v4/edge_sql/databases'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const handleApiError = (fields: string[], data: any, operation: string) => { + let error = { message: 'Error unknown', operation: operation }; + fields.forEach((field: string) => { + if (data[field]) { + const message = Array.isArray(data[field]) ? data[field].join(', ') : data[field]; + error = { + message: message, + operation: operation, + }; + } + }); + return error; +}; + /** * Creates a new Edge Database. * @param {string} token - The authorization token. @@ -30,16 +45,15 @@ const postEdgeDatabase = async (token: string, name: string, debug?: boolean): P body: JSON.stringify({ name }), }); const result = await response.json(); - if (debug) console.log('Response Post Database', JSON.stringify(result)); - if (result?.detail) { - return Promise.resolve({ - state: 'failed', - error: { - detail: result.detail, - }, - }); + if (!result.state) { + result.error = handleApiError(['detail'], result, 'post database'); + return { + error: result.error ?? JSON.stringify(result), + }; } - return Promise.resolve({ + + if (debug) console.log('Response Post Database', JSON.stringify(result)); + return { state: result.state, data: { clientId: result.data.client_id, @@ -51,7 +65,7 @@ const postEdgeDatabase = async (token: string, name: string, debug?: boolean): P status: result.data.status, updatedAt: result.data.updated_at, }, - }); + }; } catch (error) { if (debug) console.error('Error creating EdgeDB:', error); throw error; @@ -63,7 +77,7 @@ const postEdgeDatabase = async (token: string, name: string, debug?: boolean): P * @param {string} token - The authorization token. * @param {number} id - The ID of the database to delete. * @param {boolean} [debug] - Optional debug flag. - * @returns {Promise} The response from the API or null if an error occurs. + * @returns {Promise} The response from the API or error if an error occurs. */ const deleteEdgeDatabase = async (token: string, id: number, debug?: boolean): Promise => { try { @@ -74,7 +88,19 @@ const deleteEdgeDatabase = async (token: string, id: number, debug?: boolean): P }, }); if (debug) console.log('Response Delete Database:', response); - return response.json(); + const result = await response.json(); + if (!result.state) { + result.error = handleApiError(['detail'], result, 'delete database'); + return { + error: result.error ?? JSON.stringify(result), + }; + } + return { + state: result.state, + data: { + id, + }, + }; } catch (error) { if (debug) console.error('Error deleting EdgeDB:', error); throw error; @@ -105,33 +131,27 @@ const postQueryEdgeDatabase = async ( body: JSON.stringify({ statements }), }); - if (!response.ok) { - if (debug) console.error('Error querying EdgeDB:', response.statusText); + const result = await response.json(); + if (!result.data || !Array.isArray(result.data)) { + result.error = handleApiError(['detail'], result, 'post query'); return { - state: 'failed', - error: { - detail: response.statusText, - }, + error: result.error ?? JSON.stringify(result), }; } - - const json = await response.json(); - - if (json.error) { - if (debug) console.error('Error querying EdgeDB:', json.error); - return Promise.resolve({ - state: 'failed', + if (result.data[0].error) { + return { error: { - detail: json.error, + message: result.data[0].error, + operation: 'post query', }, - }); + }; } if (debug) { // limit the size of the array to 10 const limitedData: ApiQueryExecutionResponse = { - ...json, - data: (json as ApiQueryExecutionResponse)?.data?.map((data) => { + ...result, + data: (result as ApiQueryExecutionResponse)?.data?.map((data) => { return { ...data, results: { @@ -143,10 +163,10 @@ const postQueryEdgeDatabase = async ( }; console.log('Response Query:', JSON.stringify(limitedData)); } - return Promise.resolve({ - state: json.state, - data: json.data, - }); + return { + state: result.state, + data: result.data, + }; } catch (error) { if (debug) console.error('Error querying EdgeDB:', error); throw new Error((error as Error)?.message); @@ -158,13 +178,9 @@ const postQueryEdgeDatabase = async ( * @param {string} token - The authorization token. * @param {number} id - The ID of the database to retrieve. * @param {boolean} [debug] - Optional debug flag. - * @returns {Promise} The response from the API or null if an error occurs. + * @returns {Promise} The response from the API or error. */ -const getEdgeDatabaseById = async ( - token: string, - id: number, - debug?: boolean, -): Promise => { +const getEdgeDatabaseById = async (token: string, id: number, debug?: boolean): Promise => { try { const response = await fetch(`${BASE_URL}/${id}`, { method: 'GET', @@ -172,12 +188,18 @@ const getEdgeDatabaseById = async ( Authorization: `Token ${token}`, }, }); - const database = await response.json(); - if (debug) console.log('Response:', database); - return database; + const result = await response.json(); + if (!result.data) { + result.error = handleApiError(['detail'], result, 'get databases'); + return { + error: result.error ?? JSON.stringify(result), + }; + } + if (debug) console.log('Response:', result); + return result; } catch (error) { if (debug) console.error('Error getting EdgeDB:', error); - return null; + throw error; } }; @@ -192,7 +214,7 @@ const getEdgeDatabases = async ( token: string, params?: Partial, debug?: boolean, -): Promise => { +): Promise => { try { const url = new URL(BASE_URL); if (params) { @@ -209,6 +231,12 @@ const getEdgeDatabases = async ( }, }); const data = await response.json(); + if (!data.results) { + data.error = handleApiError(['detail'], data, 'get databases'); + return { + error: data.error ?? JSON.stringify(data), + }; + } if (debug) { // limit the size of the array to 10 const limitedData = { @@ -217,13 +245,6 @@ const getEdgeDatabases = async ( }; console.log('Response Databases:', JSON.stringify(limitedData)); } - if (data?.detail) { - return { - results: [], - detail: data.detail, - count: 0, - }; - } return { links: data?.links, count: data.count, diff --git a/packages/sql/src/services/api/types.ts b/packages/sql/src/services/api/types.ts index cf08883..07d41e7 100644 --- a/packages/sql/src/services/api/types.ts +++ b/packages/sql/src/services/api/types.ts @@ -11,18 +11,24 @@ export interface ApiDatabaseResponse { } export interface ApiListDatabasesResponse { - count: number; + count?: number; links?: { first: string | null; last: string | null; next: string | null; prev: string | null; }; - results: ApiDatabaseResponse[]; + results?: ApiDatabaseResponse[]; + error?: ApiError; } +export type ApiError = { + message: string; + operation: string; +}; + export interface ApiQueryExecutionResponse { - state: 'executed' | 'pending' | 'failed'; + state?: 'executed' | 'pending' | 'failed'; data?: { results: { columns: string[]; @@ -32,20 +38,17 @@ export interface ApiQueryExecutionResponse { query_duration_ms?: number; }; }[]; - error?: { - detail: string; - }; + error?: ApiError; } export interface ApiCreateDatabaseResponse { - state: 'executed' | 'pending' | 'failed'; + state?: 'executed' | 'pending' | 'failed'; data?: ApiDatabaseResponse; - error?: { - detail: string; - }; + error?: ApiError; } export interface ApiDeleteDatabaseResponse { - state: 'executed' | 'pending'; - data: null; + state?: 'executed' | 'pending'; + data?: { id: number }; + error?: ApiError; } diff --git a/packages/sql/src/services/index.ts b/packages/sql/src/services/index.ts index 6b631df..9ae1a47 100644 --- a/packages/sql/src/services/index.ts +++ b/packages/sql/src/services/index.ts @@ -1,4 +1,4 @@ -import { AzionClientOptions, AzionDatabaseQueryResponse, QueryResult } from '../types'; +import { AzionClientOptions, AzionDatabaseQueryResponse, AzionDatabaseResponse, QueryResult } from '../types'; import { limitArraySize } from '../utils'; import { toObjectQueryExecutionResponse } from '../utils/mappers/to-object'; import { getEdgeDatabases, postQueryEdgeDatabase } from './api/index'; @@ -10,23 +10,18 @@ export const apiQuery = async ( name: string, statements: string[], options?: AzionClientOptions, -): Promise => { +): Promise> => { const databaseResponse = await getEdgeDatabases(token, { search: name }, options?.debug); - if (databaseResponse?.detail) { + if (databaseResponse?.error) { return { - state: 'failed', - error: { - message: databaseResponse.detail, - operation: 'apiQuery', - }, + error: databaseResponse?.error, }; } const databases = databaseResponse?.results; if (!databases || databases.length === 0) { return { - state: 'failed', error: { message: `Database ${name} not found`, operation: 'apiQuery', @@ -37,7 +32,6 @@ export const apiQuery = async ( const database = databases[0]; if (!database?.id) { return { - state: 'failed', error: { message: `Database ${name} not found`, operation: 'apiQuery', @@ -46,36 +40,26 @@ export const apiQuery = async ( } const { state, data, error } = await postQueryEdgeDatabase(token, database.id, statements, options?.debug); - if (data) { + + if (data && data.length > 0) { const resultStatements: AzionDatabaseQueryResponse = { state, - data: data.map((result, index) => { - const info = result?.results?.query_duration_ms - ? { - durationMs: result.results.query_duration_ms, - rowsRead: result.results.rows_read, - rowsWritten: result.results.rows_written, - } - : undefined; - + results: data.map((result, index) => { return { statement: statements[index]?.split(' ')[0], - columns: result?.results?.columns, - rows: result?.results?.rows, - info, + columns: + result?.results?.columns && result?.results?.columns.length > 0 ? result?.results?.columns : undefined, + rows: result?.results?.rows && result?.results?.rows.length > 0 ? result?.results?.rows : undefined, }; }), toObject: () => toObjectQueryExecutionResponse(resultStatements), }; - return resultStatements; + return { + data: resultStatements, + }; } - return { - state, - error: { - message: error?.detail || 'Error executing query', - operation: 'executing query', - }, + error: error, }; }; @@ -87,24 +71,24 @@ export const runtimeQuery = async ( statements: string[], // eslint-disable-next-line @typescript-eslint/no-unused-vars options?: AzionClientOptions, -): Promise => { +): Promise> => { try { const internalSql = new InternalAzionSql(); const internalResult = await internalSql.query(name, statements, options); const resultStatements: AzionDatabaseQueryResponse = { - state: 'executed-runtime', - data: [], + results: [], + toObject: () => null, }; const data = await internalSql.mapperQuery(internalResult); if (data && data.length > 0) { resultStatements.state = 'executed-runtime'; - resultStatements.data = data; + resultStatements.results = data; } if (options?.debug) { // limit the size of the array to 10 const limitedData: AzionDatabaseQueryResponse = { ...resultStatements, - data: (resultStatements.data as QueryResult[]).map((data) => { + results: (resultStatements.results as QueryResult[]).map((data) => { return { ...data, rows: limitArraySize(data?.rows || [], 10), @@ -114,12 +98,13 @@ export const runtimeQuery = async ( console.log('Response Query:', JSON.stringify(limitedData)); } return { - ...resultStatements, - toObject: () => toObjectQueryExecutionResponse(resultStatements), + data: { + ...resultStatements, + toObject: () => toObjectQueryExecutionResponse(resultStatements), + }, }; } catch (error) { return { - state: 'failed', error: { message: (error as Error)?.message || 'Error executing query', operation: 'executing query', diff --git a/packages/sql/src/services/runtime/index.ts b/packages/sql/src/services/runtime/index.ts index 0daeed1..85c12be 100644 --- a/packages/sql/src/services/runtime/index.ts +++ b/packages/sql/src/services/runtime/index.ts @@ -1,5 +1,5 @@ import { Azion } from 'azion/types'; -import { AzionClientOptions, NonSelectQueryResult, QueryResult } from '../../types'; +import { AzionClientOptions, QueryResult } from '../../types'; export const getAzionSql = () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -18,10 +18,8 @@ export class InternalAzionSql { * @param {Array<{ statement: string; result: Azion.Sql.Rows }>} resultRows - The result rows from the query. * @returns {Promise>} The mapped query results. */ - mapperQuery = async ( - resultRows: { statement: string; result: Azion.Sql.Rows }[], - ): Promise<(QueryResult | NonSelectQueryResult)[]> => { - const resultStatements: QueryResult[] | NonSelectQueryResult[] = []; + mapperQuery = async (resultRows: { statement: string; result: Azion.Sql.Rows }[]): Promise => { + const resultStatements: QueryResult[] = []; for (const row of resultRows) { const columns = row.result.columnCount(); if (columns === 0) { diff --git a/packages/sql/src/types.ts b/packages/sql/src/types.ts index b505dde..79cbcd6 100644 --- a/packages/sql/src/types.ts +++ b/packages/sql/src/types.ts @@ -1,7 +1,7 @@ import { JsonObjectQueryExecutionResponse } from './utils/mappers/to-object'; -export type AzionDatabaseResponse = { - data?: AzionDatabase | AzionDatabase[] | Pick; +export type AzionDatabaseResponse = { + data?: T; error?: { message: string; operation: string; @@ -22,7 +22,7 @@ export interface AzionDatabase { * * @param {string[]} statements - An array of SQL statements to execute. * @param {AzionClientOptions} [options] - Additional options for the query execution. - * @returns {Promise} A promise that resolves to the query response or error if the operation failed. + * @returns {Promise} A promise that resolves to the query response or error if the operation faile>d. * * @example * const result = await database.query([ @@ -30,14 +30,17 @@ export interface AzionDatabase { * 'UPDATE users SET last_login = NOW() WHERE id = ?' * ], { debug: true }); */ - query?: (statements: string[], options?: AzionClientOptions) => Promise; + query: ( + statements: string[], + options?: AzionClientOptions, + ) => Promise>; /** * Executes one or more SQL statements on the database. * * @param {string[]} statements - An array of SQL statements to execute. * @param {AzionClientOptions} [options] - Additional options for the execution. - * @returns {Promise} A promise that resolves to the query response or error if the operation failed. + * @returns {Promise} A promise that resolves to the query response or error if the operation faile>d. * * @example * const result = await database.execute([ @@ -45,18 +48,21 @@ export interface AzionDatabase { * 'DELETE FROM old_users WHERE last_login < ?' * ], { force: true }); */ - execute?: (statements: string[], options?: AzionClientOptions) => Promise; + execute: ( + statements: string[], + options?: AzionClientOptions, + ) => Promise>; /** * Retrieves a list of tables in the database. * * @param {AzionClientOptions} [options] - Additional options for listing tables. - * @returns {Promise} A promise that resolves to the query response or error if the operation failed. + * @returns {Promise} A promise that resolves to the query response or error if the operation faile>d. * * @example - * const tables = await database.listTables({ debug: true }); + * const { data: tables, error } = await database.getTables({ debug: true }); */ - listTables?: (options?: AzionClientOptions) => Promise; + getTables: (options?: AzionClientOptions) => Promise>; } export type AzionQueryParams = string | number | boolean | null; @@ -66,31 +72,16 @@ export type AzionQueryExecutionParams = { params?: (AzionQueryParams | Record)[]; }; -export type AzionQueryExecutionInfo = { - rowsRead?: number; - rowsWritten?: number; - durationMs?: number; -}; - -export type NonSelectQueryResult = { - info?: AzionQueryExecutionInfo; -}; - export type QueryResult = { columns?: string[]; rows?: (number | string)[][]; statement?: string; - info?: AzionQueryExecutionInfo; }; export type AzionDatabaseQueryResponse = { - state: 'executed' | 'pending' | 'executed-runtime' | 'failed'; - data?: QueryResult[] | NonSelectQueryResult | undefined; - toObject?: () => JsonObjectQueryExecutionResponse; - error?: { - message: string; - operation: string; - }; + state?: 'pending' | 'failed' | 'executed' | 'executed-runtime'; + results?: QueryResult[]; + toObject: () => JsonObjectQueryExecutionResponse | null; }; export type AzionDatabaseExecutionResponse = AzionDatabaseQueryResponse; @@ -102,6 +93,16 @@ export type AzionDatabaseCollectionOptions = { search?: string; }; +export type AzionDatabaseCollections = { + databases?: AzionDatabase[]; + count?: number; +}; + +export type AzionDatabaseDeleteResponse = { + state: 'pending' | 'failed' | 'executed'; + id: number; +}; + export interface AzionSQLClient { /** * Creates a new database with the specified name. @@ -117,12 +118,12 @@ export interface AzionSQLClient { * console.error(`Failed to create database: ${error.message}`); * */ - createDatabase: (name: string) => Promise; + createDatabase: (name: string) => Promise>; /** * Deletes a database by its ID. * * @param {number} id - ID of the database to delete. - * @returns {Promise} Object confirming deletion or error if the operation failed. + * @returns {Promise>} Object confirming deletion or error if the operation failed. * * @example * const { data, error } = await sqlClient.deleteDatabase(123); @@ -132,12 +133,12 @@ export interface AzionSQLClient { * console.error(`Failed to delete database: ${error.message}`); * */ - deleteDatabase: (id: number) => Promise; + deleteDatabase: (id: number) => Promise>; /** * Retrieves a database by its Name. * * @param {string} name - Name of the database to retrieve. - * @returns {Promise} The retrieved database object or null if not found. + * @returns {Promise>} The retrieved database object or null if not found. * * @example * const { data, error } = await sqlClient.getDatabase('my-db'); @@ -147,7 +148,7 @@ export interface AzionSQLClient { * console.error(`Failed to retrieve database: ${error.message}`); * */ - getDatabase?: (name: string) => Promise; + getDatabase: (name: string) => Promise>; /** * Retrieves a list of databases with optional filtering and pagination. @@ -172,7 +173,7 @@ export interface AzionSQLClient { page?: number; page_size?: number; search?: string; - }) => Promise; + }) => Promise>; } /** diff --git a/packages/sql/src/utils/mappers/to-object.ts b/packages/sql/src/utils/mappers/to-object.ts index b2a6478..fc5c5ed 100644 --- a/packages/sql/src/utils/mappers/to-object.ts +++ b/packages/sql/src/utils/mappers/to-object.ts @@ -2,29 +2,22 @@ import { AzionDatabaseQueryResponse } from '../../types'; export type JsonObjectQueryExecutionResponse = { - state: 'executed' | 'pending' | 'executed-runtime' | 'failed'; - data: { + results?: { statement?: string; rows: { [key: string]: any }[]; }[]; - info?: { - rowsRead?: number; - rowsWritten?: number; - durationMs?: number; - }; }; -export const toObjectQueryExecutionResponse = ({ state, data }: AzionDatabaseQueryResponse) => { +export const toObjectQueryExecutionResponse = ({ results }: AzionDatabaseQueryResponse) => { let transformedData: any = []; - if (data instanceof Array) { - if (data.length === 0) { + if (results instanceof Array) { + if (results.length === 0) { return { - state, - data: [], + results: [], }; } let transformedRows: any = null; - transformedData = data?.map((item) => { + transformedData = results?.map((item) => { if (item?.rows) { transformedRows = item.rows.map((row) => { const obj: { [key: string]: any } = {}; @@ -43,7 +36,6 @@ export const toObjectQueryExecutionResponse = ({ state, data }: AzionDatabaseQue }); } return { - state, - data: transformedData, + results: transformedData, }; }; diff --git a/packages/storage/README.md b/packages/storage/README.md index 05bbf56..5c109e0 100644 --- a/packages/storage/README.md +++ b/packages/storage/README.md @@ -26,7 +26,7 @@ Azion Edge Storage Client provides a simple interface to interact with the Azion - [`createBucket`](#createbucket) - [`deleteBucket`](#deletebucket) - [`getBuckets`](#getbuckets) - - [`getBucket`](#getbucketbyname) + - [`getBucket`](#getbucket) - [`updateBucket`](#updatebucket) - [`createObject`](#createobject) - [`getObjectByKey`](#getobjectbykey) @@ -37,6 +37,7 @@ Azion Edge Storage Client provides a simple interface to interact with the Azion - [Types](#types) - [`ClientConfig`](#clientconfig) - [`StorageClient`](#storageclient) + - [`AzionStorageResponse`](#azionbucketresponse) - [`Bucket`](#bucket) - [`BucketObject`](#bucketobject) - [`DeletedBucket`](#deletedbucket) @@ -98,23 +99,27 @@ You can create a client instance with specific configurations. ```javascript import { createBucket } from 'azion/storage'; -const bucket = await createBucket({ name: 'my-new-bucket', edge_access: 'public' }); -if (bucket) { - console.log(`Bucket created with name: ${bucket.name}`); +const { data, error } = await createBucket({ name: 'my-new-bucket', edge_access: 'public' }); +if (data) { + console.log(`Bucket created with name: ${data.name}`); } else { - console.error('Failed to create bucket'); + console.error('Failed to create bucket', error); } ``` **TypeScript:** ```typescript -import { createBucket, AzionBucket } from 'azion/storage'; -const bucket: AzionBucket | null = await createBucket({ name: 'my-new-bucket', edge_access: 'public' }); -if (bucket) { - console.log(`Bucket created with name: ${bucket.name}`); +import { createBucket } from 'azion/storage'; +import type { AzionStorageResponse, AzionBucket } from 'azion/storage'; +const { data, error }: AzionStorageResponse = await createBucket({ + name: 'my-new-bucket', + edge_access: 'public', +}); +if (data) { + console.log(`Bucket created with name: ${data.name}`); } else { - console.error('Failed to create bucket'); + console.error('Failed to create bucket', error); } ``` @@ -125,24 +130,24 @@ if (bucket) { ```javascript import { deleteBucket } from 'azion/storage'; -const result = await deleteBucket({ name: 'my-bucket' }); -if (result) { - console.log(`Bucket ${result.name} deleted successfully`); +const { data, error } = await deleteBucket({ name: 'my-bucket' }); +if (data) { + console.log(`Bucket ${data.name} deleted successfully`); } else { - console.error('Failed to delete bucket'); + console.error('Failed to delete bucket', error); } ``` **TypeScript:** ```typescript -import { deleteBucket, AzionDeletedBucket } from 'azion/storage'; +import { deleteBucket, AzionDeletedBucket, AzionStorageResponse } from 'azion/storage'; -const result: AzionDeletedBucket | null = await deleteBucket({ name: 'my-bucket' }); -if (result) { - console.log(`Bucket ${result.name} deleted successfully`); +const { data, error }: AzionStorageResponse = await deleteBucket({ name: 'my-bucket' }); +if (data) { + console.log(`Bucket ${data.name} deleted successfully`); } else { - console.error('Failed to delete bucket'); + console.error('Failed to delete bucket', error); } ``` @@ -153,24 +158,26 @@ if (result) { ```javascript import { getBuckets } from 'azion/storage'; -const buckets = await getBuckets({ params: { page: 1, page_size: 10 } }); +const { data: buckets, error } = await getBuckets({ params: { page: 1, page_size: 10 } }); if (buckets) { - console.log(`Retrieved ${buckets.length} buckets`); + console.log(`Retrieved ${buckets.count} buckets`); } else { - console.error('Failed to retrieve buckets'); + console.error('Failed to retrieve buckets', error); } ``` **TypeScript:** ```typescript -import { getBuckets, AzionBucket } from 'azion/storage'; +import { getBuckets, AzionStorageResponse, AzionBuckets } from 'azion/storage'; -const buckets: AzionBucket[] | null = await getBuckets({ params: { page: 1, page_size: 10 } }); +const { data: buckets, error }: AzionStorageResponse = await getBuckets({ + params: { page: 1, page_size: 10 }, +}); if (buckets) { - console.log(`Retrieved ${buckets.length} buckets`); + console.log(`Retrieved ${buckets.count} buckets`); } else { - console.error('Failed to retrieve buckets'); + console.error('Failed to retrieve buckets', error); } ``` @@ -181,11 +188,11 @@ if (buckets) { ```javascript import { getBucket } from 'azion/storage'; -const bucket = await getBucket({ name: 'my-bucket' }); +const { data: bucket, error } = await getBucket({ name: 'my-bucket' }); if (bucket) { console.log(`Retrieved bucket: ${bucket.name}`); } else { - console.error('Bucket not found'); + console.error('Bucket not found', error); } ``` @@ -194,11 +201,11 @@ if (bucket) { ```typescript import { getBucket, AzionBucket } from 'azion/storage'; -const bucket: AzionBucket | null = await getBucket({ name: 'my-bucket' }); +const { data: bucket, error }: AzionStorageResponse = await getBucket({ name: 'my-bucket' }); if (bucket) { console.log(`Retrieved bucket: ${bucket.name}`); } else { - console.error('Bucket not found'); + console.error('Bucket not found', error); } ``` @@ -209,24 +216,27 @@ if (bucket) { ```javascript import { updateBucket } from 'azion/storage'; -const updatedBucket = await updateBucket({ name: 'my-bucket', edge_access: 'private' }); +const { data: updatedBucket, error } = await updateBucket({ name: 'my-bucket', edge_access: 'private' }); if (updatedBucket) { console.log(`Bucket updated: ${updatedBucket.name}`); } else { - console.error('Failed to update bucket'); + console.error('Failed to update bucket', error); } ``` **TypeScript:** ```typescript -import { updateBucket, AzionBucket } from 'azion/storage'; +import { updateBucket, AzionBucket, AzionStorageResponse } from 'azion/storage'; -const updatedBucket: AzionBucket | null = await updateBucket({ name: 'my-bucket', edge_access: 'private' }); +const { data: updatedBucket, error }: AzionStorageResponse | null = await updateBucket({ + name: 'my-bucket', + edge_access: 'private', +}); if (updatedBucket) { console.log(`Bucket updated: ${updatedBucket.name}`); } else { - console.error('Failed to update bucket'); + console.error('Failed to update bucket', error); } ``` @@ -237,21 +247,25 @@ if (updatedBucket) { ```javascript import { createObject } from 'azion/storage'; -const newObject = await createObject({ bucketName: 'my-bucket', key: 'new-file.txt', file: 'File content' }); +const { data: newObject, error } = await createObject({ + bucketName: 'my-bucket', + key: 'new-file.txt', + file: 'File content', +}); if (newObject) { console.log(`Object created with key: ${newObject.key}`); console.log(`Object content: ${newObject.content}`); } else { - console.error('Failed to create object'); + console.error('Failed to create object', error); } ``` **TypeScript:** ```typescript -import { createObject, AzionBucketObject } from 'azion/storage'; +import { createObject, AzionBucketObject, AzionStorageResponse } from 'azion/storage'; -const newObject: AzionBucketObject | null = await createObject({ +const { data: newObject, error }: AzionStorageResponse = await createObject({ bucketName: 'my-bucket', key: 'new-file.txt', file: 'File content', @@ -260,7 +274,7 @@ if (newObject) { console.log(`Object created with key: ${newObject.key}`); console.log(`Object content: ${newObject.content}`); } else { - console.error('Failed to create object'); + console.error('Failed to create object', error); } ``` @@ -271,24 +285,27 @@ if (newObject) { ```javascript import { getObjectByKey } from 'azion/storage'; -const object = await getObjectByKey({ bucketName: 'my-bucket', key: 'file.txt' }); +const { data: object, error } = await getObjectByKey({ bucketName: 'my-bucket', key: 'file.txt' }); if (object) { console.log(`Retrieved object: ${object.key}`); } else { - console.error('Object not found'); + console.error('Object not found', error); } ``` **TypeScript:** ```typescript -import { getObjectByKey, AzionBucketObject } from 'azion/storage'; +import { getObjectByKey, AzionBucketObject, AzionStorageResponse } from 'azion/storage'; -const object: AzionBucketObject | null = await getObjectByKey({ bucketName: 'my-bucket', key: 'file.txt' }); +const { data: object, error }: AzionStorageResponse = await getObjectByKey({ + bucketName: 'my-bucket', + key: 'file.txt', +}); if (object) { console.log(`Retrieved object: ${object.key}`); } else { - console.error('Object not found'); + console.error('Object not found', error); } ``` @@ -299,24 +316,26 @@ if (object) { ```javascript import { getObjects } from 'azion/storage'; -const objects = await getObjects({ bucketName: 'my-bucket' }); -if (objects) { - console.log(`Retrieved ${objects.length} objects from the bucket`); +const { data: objectsResult, error } = await getObjects({ bucketName: 'my-bucket' }); +if (objectsResult) { + console.log(`Retrieved ${objectsResult.count} objects from the bucket`); } else { - console.error('Failed to retrieve objects'); + console.error('Failed to retrieve objects', error); } ``` **TypeScript:** ```typescript -import { getObjects, AzionBucketObject } from 'azion/storage'; +import { getObjects, AzionBucketObject, AzionStorageResponse } from 'azion/storage'; -const objects: AzionBucketObject[] | null = await getObjects({ bucketName: 'my-bucket' }); -if (objects) { - console.log(`Retrieved ${objects.length} objects from the bucket`); +const { data: objectResult, error }: AzionStorageResponse = await getObjects({ + bucketName: 'my-bucket', +}); +if (objectResult) { + console.log(`Retrieved ${objectResult.count} objects from the bucket`); } else { - console.error('Failed to retrieve objects'); + console.error('Failed to retrieve objects', error); } ``` @@ -327,12 +346,16 @@ if (objects) { ```javascript import { updateObject } from 'azion/storage'; -const updatedObject = await updateObject({ bucketName: 'my-bucket', key: 'file.txt', file: 'Updated content' }); +const { data: updatedObject, error } = await updateObject({ + bucketName: 'my-bucket', + key: 'file.txt', + file: 'Updated content', +}); if (updatedObject) { console.log(`Object updated: ${updatedObject.key}`); console.log(`New content: ${updatedObject.content}`); } else { - console.error('Failed to update object'); + console.error('Failed to update object', error); } ``` @@ -341,7 +364,7 @@ if (updatedObject) { ```typescript import { updateObject, AzionBucketObject } from 'azion/storage'; -const updatedObject: AzionBucketObject | null = await updateObject({ +const { data: updatedObject, error }: AzionStorageResponse = await updateObject({ bucketName: 'my-bucket', key: 'file.txt', file: 'Updated content', @@ -350,7 +373,7 @@ if (updatedObject) { console.log(`Object updated: ${updatedObject.key}`); console.log(`New content: ${updatedObject.content}`); } else { - console.error('Failed to update object'); + console.error('Failed to update object', error); } ``` @@ -361,24 +384,27 @@ if (updatedObject) { ```javascript import { deleteObject } from 'azion/storage'; -const result = await deleteObject({ bucketName: 'my-bucket', key: 'file.txt' }); +const { data: result, error } = await deleteObject({ bucketName: 'my-bucket', key: 'file.txt' }); if (result) { console.log(`Object ${result.key} deleted successfully`); } else { - console.error('Failed to delete object'); + console.error('Failed to delete object', error); } ``` **TypeScript:** ```typescript -import { deleteObject, AzionDeletedBucketObject } from 'azion/storage'; +import { deleteObject, AzionDeletedBucketObject, AzionStorageResponse } from 'azion/storage'; -const result: AzionDeletedBucketObject | null = await deleteObject({ bucketName: 'my-bucket', key: 'file.txt' }); +const { data: result, error }: AzionStorageResponse = await deleteObject({ + bucketName: 'my-bucket', + key: 'file.txt', +}); if (result) { console.log(`Object ${result.key} deleted successfully`); } else { - console.error('Failed to delete object'); + console.error('Failed to delete object', error); } ``` @@ -391,45 +417,59 @@ import { createClient } from 'azion/storage'; const client = createClient({ token: 'your-api-token', debug: true }); -const newBucket = await client.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); -if (newBucket) { - console.log(`Bucket created with name: ${newBucket.name}`); +const { data, error } = await client.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); +if (data) { + console.log(`Bucket created with name: ${data.name}`); } -const allBuckets = await client.getBuckets(); +const { data: allBuckets } = await client.getBuckets(); if (allBuckets) { - console.log(`Retrieved ${allBuckets.length} buckets`); + console.log(`Retrieved ${allBuckets.count} buckets`); } -const newObject = await client.createObject({ bucketName: 'my-new-bucket', key: 'new-file.txt', file: 'File content' }); +const { data: newObject } = await client.createObject({ + bucketName: 'my-new-bucket', + key: 'new-file.txt', + file: 'File content', +}); if (newObject) { console.log(`Object created with key: ${newObject.key}`); } -const queryResult = await newObject.updateObject({ key: 'new-file.txt', file: 'Updated content' }); -if (queryResult) { - console.log(`Object updated with key: ${queryResult.key}`); +const { data: updateResult } = await newObject.updateObject({ key: 'new-file.txt', file: 'Updated content' }); +if (updateResult) { + console.log(`Object updated with key: ${updateResult.key}`); } ``` **TypeScript:** ```typescript -import { createClient, StorageClient, AzionBucket, AzionBucketObject } from 'azion/storage'; +import { + createClient, + StorageClient, + AzionStorageResponse, + AzionBucket, + AzionBucketObject, + AzionBuckets, +} from 'azion/storage'; const client: StorageClient = createClient({ token: 'your-api-token', debug: true }); -const newBucket: AzionBucket | null = await client.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); -if (newBucket) { - console.log(`Bucket created with name: ${newBucket.name}`); +const { data, error }: AzionStorageResponse = await client.createBucket({ + name: 'my-new-bucket', + edge_access: 'public', +}); +if (data) { + console.log(`Bucket created with name: ${data.name}`); } -const allBuckets: AzionBucket[] | null = await client.getBuckets(); +const { data: allBuckets }: AzionStorageResponse = await client.getBuckets(); if (allBuckets) { - console.log(`Retrieved ${allBuckets.length} buckets`); + console.log(`Retrieved ${allBuckets.count} buckets`); } -const newObject: AzionBucketObject | null = await client.createObject({ +const { data: newObject }: AzionStorageResponse = await client.createObject({ bucketName: 'my-new-bucket', key: 'new-file.txt', file: 'File content', @@ -438,12 +478,234 @@ if (newObject) { console.log(`Object created with key: ${newObject.key}`); } -const queryResult: AzionBucketObject | null = await client.updateObject({ +const { data: updateResult }: AzionStorageResponse = await client.updateObject({ bucketName: 'my-new-bucket', key: 'new-file.txt', file: 'Updated content', }); -if (queryResult) { - console.log(`Object updated with key: ${queryResult.key}`); +if (updateResult) { + console.log(`Object updated with key: ${updateResult.key}`); } ``` + +## API Reference + +### `createBucket` + +Creates a new bucket. + +**Parameters:** + +- `name: string` - Name of the new bucket. +- `edge_access: string` - Edge access configuration for the bucket. +- `options?: AzionClientOptions` - Optional parameters for the request. + +**Returns:** + +- `Promise>` - The created bucket object or error. + +### `deleteBucket` + +Deletes a bucket by its name. + +**Parameters:** + +- `name: string` - Name of the bucket to delete. +- `debug?: boolean` - Enable debug mode for detailed logging. + +**Returns:** + +- `Promise>` - Object confirming deletion or error. + +### `getBuckets` + +Retrieves a list of buckets with optional filtering and pagination. + +**Parameters:** + +- `options?: AzionBucketCollectionOptions` - Optional parameters for filtering and pagination. + - `page?: number` - Page number for pagination. + - `page_size?: number` - Number of items per page. +- `debug?: boolean` - Enable debug mode for detailed logging. + +**Returns:** + +- `Promise>` - Array of bucket objects or error. + +### `getBucket` + +Retrieves a bucket by its name. + +**Parameters:** + +- `name: string` - Name of the bucket to retrieve. +- `debug?: boolean` - Enable debug mode for detailed logging. + +**Returns:** + +- `Promise>` - The retrieved bucket object or error if not found. + +### `updateBucket` + +Updates an existing bucket. + +**Parameters:** + +- `name: string` - Name of the bucket to update. +- `edge_access: string` - New edge access configuration for the bucket. +- `debug?: boolean` - Enable debug mode for detailed logging. + +**Returns:** + +- `Promise>` - The updated bucket object or error if update failed. + +### `createObject` + +Creates a new object in a specific bucket. + +**Parameters:** + +- `bucketName: string` - Name of the bucket to create the object in. +- `objectKey: string` - Key (name) of the object to create. +- `file: string` - Content of the file to upload. +- `debug?: boolean` - Enable debug mode for detailed logging. + +**Returns:** + +- `Promise` - The created object or null if creation failed. + +### `getObjectByKey` + +Retrieves an object from a specific bucket by its key. + +**Parameters:** + +- `bucketName: string` - Name of the bucket containing the object. +- `objectKey: string` - Key of the object to retrieve. +- `debug?: boolean` - Enable debug mode for detailed logging. + +**Returns:** + +- `Promise` - The retrieved object or null if not found. + +### `getObjects` + +Retrieves a list of objects in a specific bucket. + +**Parameters:** + +- `bucketName: string` - Name of the bucket to retrieve objects from. +- `debug?: boolean` - Enable debug mode for detailed logging. + +**Returns:** + +- `Promise>` - Array of bucket objects or error. + +### `updateObject` + +Updates an existing object in a specific bucket. + +**Parameters:** + +- `bucketName: string` - Name of the bucket containing the object. +- `objectKey: string` - Key of the object to update. +- `file: string` - New content of the file. +- `debug?: boolean` - Enable debug mode for detailed logging. + +**Returns:** + +- `Promise>` - The updated object or error if update failed. + +### `deleteObject` + +Deletes an object from a specific bucket. + +**Parameters:** + +- `bucketName: string` - Name of the bucket containing the object. +- `objectKey: string` - Key of the object to delete. +- `debug?: boolean` - Enable debug mode for detailed logging. + +**Returns:** + +- `Promise>` - Confirmation of deletion or error if deletion failed. + +### `createClient` + +Creates a Storage client with methods to interact with Azion Edge Storage. + +**Parameters:** + +- `config?: Partial<{ token: string; debug: boolean }>` - Configuration options for the Storage client. + +**Returns:** + +- `StorageClient` - An object with methods to interact with Storage. + +## Types + +### `ClientConfig` + +Configuration options for the Storage client. + +- `token?: string` - Your Azion API token. +- `debug?: boolean` - Enable debug mode for detailed logging. + +### `StorageClient` + +An object with methods to interact with Storage. + +- `getBuckets: (options?: BucketCollectionOptions) => Promise>` +- `createBucket: (name: string, edge_access: string) => Promise>` +- `updateBucket: (name: string, edge_access: string) => Promise>` +- `deleteBucket: (name: string) => Promise>` +- `getBucket: (name: string) => Promise>` + +### `AzionStorageResponse` + +The response object from a bucket operation. + +- `data?: T` - The data generic object. +- `error?: { message: string; operation: string;}` + +### `AzionBucket` + +The bucket object. + +- `name: string` +- `edge_access?: string` +- `state?: 'executed' | 'pending'` +- `getObjects?: () => Promise>` +- `getObjectByKey?: (objectKey: string) => Promise>` +- `createObject?: (objectKey: string, file: string) => Promise>` +- `updateObject?: (objectKey: string, file: string) => Promise>` +- `deleteObject?: (objectKey: string) => Promise>` + +### `AzionBucketObject` + +The bucket object. + +- `key: string` +- `state?: 'executed' | 'pending'` +- `size?: number` +- `last_modified?: string` +- `content_type?: string` +- `content?: string` + +### `AzionDeletedBucket` + +The response object from a delete bucket request. + +- `name: string` +- `state: 'executed' | 'pending'` + +### `AzionDeletedBucketObject` + +The response object from a delete object request. + +- `key: string` +- `state: 'executed' | 'pending'` + +## Contributing + +Feel free to submit issues or pull requests to improve the functionality or documentation. diff --git a/packages/storage/src/index.test.ts b/packages/storage/src/index.test.ts index 94e62f9..cb53771 100644 --- a/packages/storage/src/index.test.ts +++ b/packages/storage/src/index.test.ts @@ -55,15 +55,17 @@ describe('Storage Module', () => { (services.postBucket as jest.Mock).mockResolvedValue(mockResponse); const result = await createBucket({ name: 'test-bucket', edge_access: 'public', options: { debug } }); - expect(result).toEqual(expect.objectContaining({ name: 'test-bucket', edge_access: 'public' })); + expect(result.data).toEqual(expect.objectContaining({ name: 'test-bucket', edge_access: 'public' })); expect(services.postBucket).toHaveBeenCalledWith(mockToken, 'test-bucket', 'public', debug); }); - it('should return null on failure', async () => { - (services.postBucket as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.postBucket as jest.Mock).mockResolvedValue({ + error: { message: 'token invalid', operation: 'create bucket' }, + }); const result = await createBucket({ name: 'test-bucket', edge_access: 'public', options: { debug } }); - expect(result).toBeNull(); + expect(result).toEqual({ error: { message: 'token invalid', operation: 'create bucket' } }); }); }); @@ -73,15 +75,17 @@ describe('Storage Module', () => { (services.deleteBucket as jest.Mock).mockResolvedValue(mockResponse); const result = await deleteBucket({ name: 'test-bucket', options: { debug } }); - expect(result).toEqual({ name: 'test-bucket', state: 'success' }); + expect(result.data).toEqual({ name: 'test-bucket', state: 'success' }); expect(services.deleteBucket).toHaveBeenCalledWith(mockToken, 'test-bucket', debug); }); - it('should return null on failure', async () => { - (services.deleteBucket as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.deleteBucket as jest.Mock).mockResolvedValue({ + error: { message: 'token invalid', operation: 'delete bucket' }, + }); const result = await deleteBucket({ name: 'test-bucket', options: { debug } }); - expect(result).toBeNull(); + expect(result.error).toEqual({ message: 'token invalid', operation: 'delete bucket' }); }); }); @@ -91,16 +95,18 @@ describe('Storage Module', () => { (services.getBuckets as jest.Mock).mockResolvedValue(mockResponse); const result = await getBuckets({ params: { page: 1, page_size: 10 }, options: { debug } }); - expect(result).toHaveLength(2); - expect(result![0]).toHaveProperty('name', 'bucket1'); + expect(result.data?.buckets).toHaveLength(2); + expect(result.data?.buckets[0]).toHaveProperty('name', 'bucket1'); expect(services.getBuckets).toHaveBeenCalledWith(mockToken, { page: 1, page_size: 10 }, debug); }); - it('should return null on failure', async () => { - (services.getBuckets as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.getBuckets as jest.Mock).mockResolvedValue({ + error: { message: 'token invalid', operation: 'get buckets' }, + }); const result = await getBuckets({ params: { page: 1, page_size: 10 }, options: { debug } }); - expect(result).toBeNull(); + expect(result.error).toEqual({ message: 'token invalid', operation: 'get buckets' }); }); }); @@ -110,16 +116,16 @@ describe('Storage Module', () => { (services.getBuckets as jest.Mock).mockResolvedValue(mockResponse); const result = await getBucket({ name: 'test-bucket', options: { debug } }); - expect(result).toEqual(expect.objectContaining({ name: 'test-bucket', edge_access: 'public' })); + expect(result.data).toEqual(expect.objectContaining({ name: 'test-bucket', edge_access: 'public' })); expect(services.getBuckets).toHaveBeenCalledWith(mockToken, { page_size: 1000000 }, debug); }); - it('should return null if bucket is not found', async () => { + it('should return error if bucket is not found', async () => { const mockResponse = { results: [] }; (services.getBuckets as jest.Mock).mockResolvedValue(mockResponse); const result = await getBucket({ name: 'non-existent-bucket', options: { debug } }); - expect(result).toBeNull(); + expect(result.error).toEqual({ message: 'Bucket not found', operation: 'get bucket' }); }); }); @@ -129,15 +135,17 @@ describe('Storage Module', () => { (services.patchBucket as jest.Mock).mockResolvedValue(mockResponse); const result = await updateBucket({ name: 'test-bucket', edge_access: 'private', options: { debug } }); - expect(result).toEqual(expect.objectContaining({ name: 'test-bucket', edge_access: 'private' })); + expect(result.data).toEqual(expect.objectContaining({ name: 'test-bucket', edge_access: 'private' })); expect(services.patchBucket).toHaveBeenCalledWith(mockToken, 'test-bucket', 'private', debug); }); - it('should return null on failure', async () => { - (services.patchBucket as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.patchBucket as jest.Mock).mockResolvedValue({ + error: { message: 'token invalid', operation: 'update bucket' }, + }); const result = await updateBucket({ name: 'test-bucket', edge_access: 'private', options: { debug } }); - expect(result).toBeNull(); + expect(result.error).toEqual({ message: 'token invalid', operation: 'update bucket' }); }); }); @@ -152,12 +160,14 @@ describe('Storage Module', () => { content: 'file-content', options: { debug }, }); - expect(result).toEqual({ key: 'test-object', content: 'file-content', state: 'success' }); + expect(result.data).toEqual({ key: 'test-object', content: 'file-content', state: 'success' }); expect(services.postObject).toHaveBeenCalledWith(mockToken, 'test-bucket', 'test-object', 'file-content', debug); }); - it('should return null on failure', async () => { - (services.postObject as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.postObject as jest.Mock).mockResolvedValue({ + error: { message: 'token invalid', operation: 'create object' }, + }); const result = await createObject({ bucket: 'test-bucket', @@ -165,43 +175,47 @@ describe('Storage Module', () => { content: 'file-content', options: { debug }, }); - expect(result).toBeNull(); + expect(result).toEqual({ error: { message: 'token invalid', operation: 'create object' } }); }); }); describe('deleteObject', () => { it('should successfully delete an object', async () => { - const mockResponse = { state: 'success' }; + const mockResponse = { data: { state: 'success' } }; (services.deleteObject as jest.Mock).mockResolvedValue(mockResponse); const result = await deleteObject({ bucket: 'test-bucket', key: 'test-object', options: { debug } }); - expect(result).toEqual({ key: 'test-object', state: 'success' }); + expect(result.data).toEqual({ key: 'test-object' }); expect(services.deleteObject).toHaveBeenCalledWith(mockToken, 'test-bucket', 'test-object', debug); }); - it('should return null on failure', async () => { - (services.deleteObject as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.deleteObject as jest.Mock).mockResolvedValue({ + error: { message: 'token invalid', operation: 'delete object' }, + }); const result = await deleteObject({ bucket: 'test-bucket', key: 'test-object', options: { debug } }); - expect(result).toBeNull(); + expect(result).toEqual({ error: { message: 'token invalid', operation: 'delete object' } }); }); }); describe('getObjectByKey', () => { it('should successfully get an object by key', async () => { - const mockResponse = 'file-content'; + const mockResponse = { data: 'file-content' }; (services.getObjectByKey as jest.Mock).mockResolvedValue(mockResponse); const result = await getObjectByKey({ bucket: 'test-bucket', key: 'test-object', options: { debug } }); - expect(result).toEqual({ key: 'test-object', content: 'file-content' }); + expect(result.data).toEqual({ key: 'test-object', content: 'file-content' }); expect(services.getObjectByKey).toHaveBeenCalledWith(mockToken, 'test-bucket', 'test-object', debug); }); - it('should return null on failure', async () => { - (services.getObjectByKey as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.getObjectByKey as jest.Mock).mockResolvedValue({ + error: { message: 'token invalid', operation: 'get object by key' }, + }); const result = await getObjectByKey({ bucket: 'test-bucket', key: 'test-object', options: { debug } }); - expect(result).toBeNull(); + expect(result.error).toEqual({ message: 'token invalid', operation: 'get object by key' }); }); }); @@ -216,16 +230,18 @@ describe('Storage Module', () => { (services.getObjects as jest.Mock).mockResolvedValue(mockResponse); const result = await getObjects({ bucket: 'test-bucket', params: { max_object_count: 50 }, options: { debug } }); - expect(result).toHaveLength(2); - expect(result![0]).toEqual(expect.objectContaining({ key: 'object1' })); + expect(result.data?.objects).toHaveLength(2); + expect(result.data?.objects![0]).toEqual(expect.objectContaining({ key: 'object1' })); expect(services.getObjects).toHaveBeenCalledWith(mockToken, 'test-bucket', { max_object_count: 50 }, debug); }); - it('should return null on failure', async () => { - (services.getObjects as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.getObjects as jest.Mock).mockResolvedValue({ + error: { message: 'token invalid', operation: 'get objects' }, + }); const result = await getObjects({ bucket: 'test-bucket', params: { max_object_count: 50 }, options: { debug } }); - expect(result).toBeNull(); + expect(result.error).toEqual({ message: 'token invalid', operation: 'get objects' }); }); }); @@ -240,7 +256,7 @@ describe('Storage Module', () => { content: 'updated-content', options: { debug }, }); - expect(result).toEqual({ key: 'test-object', content: 'updated-content', state: 'success' }); + expect(result.data).toEqual({ key: 'test-object', content: 'updated-content', state: 'success' }); expect(services.putObject).toHaveBeenCalledWith( mockToken, 'test-bucket', @@ -250,8 +266,10 @@ describe('Storage Module', () => { ); }); - it('should return null on failure', async () => { - (services.putObject as jest.Mock).mockResolvedValue(null); + it('should return error on failure', async () => { + (services.putObject as jest.Mock).mockResolvedValue({ + error: { message: 'invalid token', operation: 'update object' }, + }); const result = await updateObject({ bucket: 'test-bucket', @@ -259,7 +277,7 @@ describe('Storage Module', () => { content: 'updated-content', options: { debug }, }); - expect(result).toBeNull(); + expect(result).toEqual({ error: { message: 'invalid token', operation: 'update object' } }); }); }); diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts index cb23ab1..3d74f05 100644 --- a/packages/storage/src/index.ts +++ b/packages/storage/src/index.ts @@ -8,16 +8,19 @@ import { postBucket, postObject, putObject, -} from './services//api/index'; +} from './services/api/index'; import { AzionBucket, AzionBucketCollectionParams, AzionBucketObject, + AzionBucketObjects, + AzionBuckets, AzionClientOptions, AzionDeletedBucket, AzionDeletedBucketObject, AzionObjectCollectionParams, AzionStorageClient, + AzionStorageResponse, CreateAzionStorageClient, } from './types'; @@ -55,31 +58,48 @@ const createInternalOrExternalMethod = any>(inter * @param {string} name - Name of the bucket to create. * @param {string} edge_access - Edge access configuration for the bucket. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The created bucket object or null if creation failed. + * @returns {Promise} The created bucket object or error message. */ export const createBucketMethod = async ( token: string, name: string, edge_access: string, options?: AzionClientOptions, -): Promise => { +): Promise> => { const apiResponse = await postBucket(resolveToken(token), name, edge_access, resolveDebug(options?.debug)); - if (apiResponse) { + if (apiResponse.data) { return { - ...apiResponse.data, - getObjects: ({ params }: { params: AzionObjectCollectionParams }): Promise => - getObjectsMethod(token, name, params), - getObjectByKey: ({ key }: { key: string }): Promise => - getObjectByKeyMethod(token, name, key), - createObject: ({ key, content }: { key: string; content: string }): Promise => - createObjectMethod(token, name, key, content), - updateObject: ({ key, content }: { key: string; content: string }): Promise => - updateObjectMethod(token, name, key, content), - deleteObject: ({ key }: { key: string }): Promise => - deleteObjectMethod(token, name, key), + data: { + ...apiResponse.data, + getObjects: ({ + params, + }: { + params: AzionObjectCollectionParams; + }): Promise> => getObjectsMethod(token, name, params), + getObjectByKey: ({ key }: { key: string }): Promise> => + getObjectByKeyMethod(token, name, key), + createObject: ({ + key, + content, + }: { + key: string; + content: string; + }): Promise> => createObjectMethod(token, name, key, content), + updateObject: ({ + key, + content, + }: { + key: string; + content: string; + }): Promise> => updateObjectMethod(token, name, key, content), + deleteObject: ({ key }: { key: string }): Promise> => + deleteObjectMethod(token, name, key), + }, }; } - return null; + return { + error: apiResponse.error, + }; }; /** @@ -88,18 +108,20 @@ export const createBucketMethod = async ( * @param {string} token - Authentication token for Azion API. * @param {string} name - Name of the bucket to delete. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} Confirmation of deletion or null if deletion failed. + * @returns {Promise>} Confirmation of deletion or error message. */ export const deleteBucketMethod = async ( token: string, name: string, options?: AzionClientOptions, -): Promise => { +): Promise> => { const apiResponse = await deleteBucket(resolveToken(token), name, resolveDebug(options?.debug)); - if (apiResponse) { - return { name: apiResponse.data.name, state: apiResponse.state }; + if (apiResponse.data) { + return { data: { name: apiResponse.data.name, state: apiResponse.state } }; } - return null; + return { + error: apiResponse.error, + }; }; /** @@ -108,30 +130,51 @@ export const deleteBucketMethod = async ( * @param {string} token - Authentication token for Azion API. * @param {AzionBucketCollectionParams} [params] - Optional parameters for filtering and pagination. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} Array of bucket objects or null if retrieval failed. + * @returns {Promise>} Array of bucket objects or error message. */ export const getBucketsMethod = async ( token: string, params?: AzionBucketCollectionParams, options?: AzionClientOptions, -): Promise => { +): Promise> => { const apiResponse = await getBuckets(resolveToken(token), params, resolveDebug(options?.debug)); - if (apiResponse) { - return apiResponse.results?.map((bucket) => ({ + if (apiResponse?.results && apiResponse.results.length > 0) { + const buckets = apiResponse.results?.map((bucket) => ({ ...bucket, - getObjects: ({ params }: { params: AzionObjectCollectionParams }): Promise => - getObjectsMethod(token, bucket.name, params), - getObjectByKey: ({ key }: { key: string }): Promise => + getObjects: ({ + params, + }: { + params: AzionObjectCollectionParams; + }): Promise> => getObjectsMethod(token, bucket.name, params), + getObjectByKey: ({ key }: { key: string }): Promise> => getObjectByKeyMethod(token, bucket.name, key), - createObject: ({ key, content }: { key: string; content: string }): Promise => - createObjectMethod(token, bucket.name, key, content), - updateObject: ({ key, content }: { key: string; content: string }): Promise => - updateObjectMethod(token, bucket.name, key, content), - deleteObject: ({ key }: { key: string }): Promise => + createObject: ({ + key, + content, + }: { + key: string; + content: string; + }): Promise> => createObjectMethod(token, bucket.name, key, content), + updateObject: ({ + key, + content, + }: { + key: string; + content: string; + }): Promise> => updateObjectMethod(token, bucket.name, key, content), + deleteObject: ({ key }: { key: string }): Promise> => deleteObjectMethod(token, bucket.name, key), })); + return { + data: { + buckets, + count: apiResponse.count ?? buckets.length, + }, + }; } - return null; + return { + error: apiResponse.error, + }; }; /** @@ -140,38 +183,79 @@ export const getBucketsMethod = async ( * @param {string} token - Authentication token for Azion API. * @param {string} name - Name of the bucket to get. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} - Bucket object or null if retrieval failed. + * @returns {Promise>} - Bucket object or error message. */ const getBucketMethod = createInternalOrExternalMethod( - async (token: string, name: string, options?: AzionClientOptions): Promise => { + async (token: string, name: string, options?: AzionClientOptions): Promise> => { // NOTE: This is a temporary solution because the API does not provide an endpoint // to search for a single bucket by name. When available, it must be replaced // by a direct API call. const bucket = await findBucketByName(token, name, options); - if (!bucket) return null; + if (bucket.error || !bucket.data?.name) { + return { + error: { + message: bucket.error?.message ?? 'Bucket not found', + operation: 'get bucket', + }, + }; + } const internalClient = new InternalStorageClient(token, options?.debug); - return internalClient.getBucket({ name }); + const internalResult = await internalClient.getBucket({ name }); + if (internalResult) { + return { + data: internalResult, + }; + } + return { + error: { + message: 'Failed to retrieve bucket', + operation: 'get bucket', + }, + }; }, - async (token: string, name: string, options?: AzionClientOptions): Promise => { + async (token: string, name: string, options?: AzionClientOptions): Promise> => { // NOTE: This is a temporary solution because the API does not provide an endpoint // to search for a single bucket by name. When available, it must be replaced // by a direct API call. const bucket = await findBucketByName(token, name, options); - if (!bucket) return null; + if (bucket.error || !bucket.data?.name) { + return { + error: { + message: bucket.error?.message ?? 'Bucket not found', + operation: 'get bucket', + }, + }; + } return { - ...bucket, - getObjects: ({ params }: { params: AzionObjectCollectionParams }): Promise => - getObjectsMethod(token, name, params), - getObjectByKey: ({ key }: { key: string }): Promise => - getObjectByKeyMethod(token, name, key), - createObject: ({ key, content }: { key: string; content: string }): Promise => - createObjectMethod(token, name, key, content), - updateObject: ({ key, content }: { key: string; content: string }): Promise => - updateObjectMethod(token, name, key, content), - deleteObject: ({ key }: { key: string }): Promise => - deleteObjectMethod(token, name, key), + data: { + name: bucket.data.name, + edge_access: bucket.data?.edge_access, + getObjects: ({ + params, + }: { + params: AzionObjectCollectionParams; + }): Promise> => getObjectsMethod(token, name, params), + getObjectByKey: ({ key }: { key: string }): Promise> => + getObjectByKeyMethod(token, name, key), + createObject: ({ + key, + content, + }: { + key: string; + content: string; + }): Promise> => createObjectMethod(token, name, key, content), + updateObject: ({ + key, + content, + }: { + key: string; + content: string; + }): Promise> => updateObjectMethod(token, name, key, content), + deleteObject: ({ key }: { key: string }): Promise> => + deleteObjectMethod(token, name, key), + }, }; }, ); @@ -183,31 +267,48 @@ const getBucketMethod = createInternalOrExternalMethod( * @param {string} name - Name of the bucket to update. * @param {string} edge_access - New edge access configuration for the bucket. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The updated bucket or null if update failed. + * @returns {Promise>} The updated bucket or error message. */ export const updateBucketMethod = async ( token: string, name: string, edge_access: string, options?: AzionClientOptions, -): Promise => { +): Promise> => { const apiResponse = await patchBucket(resolveToken(token), name, edge_access, resolveDebug(options?.debug)); - if (apiResponse) { + if (apiResponse?.data) { return { - ...apiResponse.data, - getObjects: ({ params }: { params: AzionObjectCollectionParams }): Promise => - getObjectsMethod(token, name, params), - getObjectByKey: ({ key }: { key: string }): Promise => - getObjectByKeyMethod(token, name, key), - createObject: ({ key, content }: { key: string; content: string }): Promise => - createObjectMethod(token, name, key, content), - updateObject: ({ key, content }: { key: string; content: string }): Promise => - updateObjectMethod(token, name, key, content), - deleteObject: ({ key }: { key: string }): Promise => - deleteObjectMethod(token, name, key), + data: { + ...apiResponse.data, + getObjects: ({ + params, + }: { + params: AzionObjectCollectionParams; + }): Promise> => getObjectsMethod(token, name, params), + getObjectByKey: ({ key }: { key: string }): Promise> => + getObjectByKeyMethod(token, name, key), + createObject: ({ + key, + content, + }: { + key: string; + content: string; + }): Promise> => createObjectMethod(token, name, key, content), + updateObject: ({ + key, + content, + }: { + key: string; + content: string; + }): Promise> => updateObjectMethod(token, name, key, content), + deleteObject: ({ key }: { key: string }): Promise> => + deleteObjectMethod(token, name, key), + }, }; } - return null; + return { + error: apiResponse.error, + }; }; /** @@ -217,7 +318,7 @@ export const updateBucketMethod = async ( * @param {string} bucket - Name of the bucket to retrieve objects from. * @param {AzionObjectCollectionParams} [params] - Optional parameters for object collection. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} Array of bucket objects or null if retrieval failed. + * @returns {Promise>} Array of bucket objects or null if retrieval failed. */ const getObjectsMethod = createInternalOrExternalMethod( async ( @@ -225,19 +326,40 @@ const getObjectsMethod = createInternalOrExternalMethod( bucket: string, params?: AzionObjectCollectionParams, options?: AzionClientOptions, - ): Promise => { + ): Promise> => { const internalClient = new InternalStorageClient(token, options?.debug); internalClient.name = bucket; - return internalClient.getObjects({ params }); + const internalResult = await internalClient.getObjects({ params }); + if (internalResult.data) { + return { + data: internalResult.data, + }; + } + return { + error: { + message: 'Failed to retrieve objects', + operation: 'get objects', + }, + }; }, async ( token: string, bucket: string, params?: AzionObjectCollectionParams, options?: AzionClientOptions, - ): Promise => { + ): Promise> => { const apiResponse = await getObjects(resolveToken(token), bucket, params, options?.debug); - return apiResponse?.results ?? null; + if (apiResponse.results) { + return { + data: { + objects: apiResponse.results, + count: apiResponse.results.length, + }, + }; + } + return { + error: apiResponse?.error, + }; }, ); @@ -248,7 +370,7 @@ const getObjectsMethod = createInternalOrExternalMethod( * @param {string} bucket - Name of the bucket containing the object. * @param {string} key - Key of the object to retrieve. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The retrieved object or null if not found. + * @returns {Promise>} The retrieved object or error message. */ const getObjectByKeyMethod = createInternalOrExternalMethod( async ( @@ -256,19 +378,40 @@ const getObjectByKeyMethod = createInternalOrExternalMethod( bucket: string, key: string, options?: AzionClientOptions, - ): Promise => { + ): Promise> => { const internalClient = new InternalStorageClient(token, options?.debug); internalClient.name = bucket; - return internalClient.getObjectByKey({ key }); + const internalResult = await internalClient.getObjectByKey({ key }); + if (internalResult.data) { + return { + data: internalResult.data, + }; + } + return { + error: { + message: 'Failed to retrieve object', + operation: 'get object by key', + }, + }; }, async ( token: string, bucket: string, key: string, options?: AzionClientOptions, - ): Promise => { + ): Promise> => { const apiResponse = await getObjectByKey(resolveToken(token), bucket, key, resolveDebug(options?.debug)); - return apiResponse ? { key: key, content: apiResponse } : null; + if (apiResponse.data) { + return { + data: { + key, + content: apiResponse.data, + }, + }; + } + return { + error: apiResponse.error, + }; }, ); @@ -280,7 +423,7 @@ const getObjectByKeyMethod = createInternalOrExternalMethod( * @param {string} key - Key (name) of the object to create. * @param {string} content - Content of the content to upload. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The created object or null if creation failed. + * @returns {Promise>} The created object or error message */ const createObjectMethod = createInternalOrExternalMethod( async ( @@ -289,10 +432,21 @@ const createObjectMethod = createInternalOrExternalMethod( key: string, content: string, options?: AzionClientOptions, - ): Promise => { + ): Promise> => { const internalClient = new InternalStorageClient(token, options?.debug); internalClient.name = bucket; - return internalClient.createObject({ key, content }); + const internalResult = await internalClient.createObject({ key, content }); + if (internalResult?.data) { + return { + data: internalResult.data, + }; + } + return { + error: { + message: 'Failed to create object', + operation: 'create object', + }, + }; }, async ( token: string, @@ -300,9 +454,20 @@ const createObjectMethod = createInternalOrExternalMethod( key: string, content: string, options?: AzionClientOptions, - ): Promise => { + ): Promise> => { const apiResponse = await postObject(resolveToken(token), bucket, key, content, resolveDebug(options?.debug)); - return apiResponse ? { key: apiResponse.data.object_key, content: content, state: apiResponse.state } : null; + if (apiResponse.data) { + return { + data: { + key: apiResponse.data.object_key, + content, + state: apiResponse.state, + }, + }; + } + return { + error: apiResponse.error, + }; }, ); @@ -314,7 +479,7 @@ const createObjectMethod = createInternalOrExternalMethod( * @param {string} key - Key of the object to update. * @param {string} content - New content of the content. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} The updated object or null if update failed. + * @returns {Promise>} The updated object or error message. */ const updateObjectMethod = createInternalOrExternalMethod( async ( @@ -323,10 +488,18 @@ const updateObjectMethod = createInternalOrExternalMethod( key: string, content: string, options?: AzionClientOptions, - ): Promise => { + ): Promise> => { const internalClient = new InternalStorageClient(token, options?.debug); internalClient.name = bucket; - return internalClient.updateObject({ key, content }); + const internalResult = await internalClient.updateObject({ key, content }); + if (internalResult?.data) { + return { + data: internalResult.data, + }; + } + return { + error: internalResult.error, + }; }, async ( token: string, @@ -334,9 +507,20 @@ const updateObjectMethod = createInternalOrExternalMethod( key: string, content: string, options?: AzionClientOptions, - ): Promise => { + ): Promise> => { const apiResponse = await putObject(resolveToken(token), bucket, key, content, resolveDebug(options?.debug)); - return apiResponse ? { key: apiResponse.data.object_key, content: content, state: apiResponse.state } : null; + if (apiResponse.data) { + return { + data: { + key: apiResponse.data.object_key, + content, + state: apiResponse.state, + }, + }; + } + return { + error: apiResponse.error, + }; }, ); @@ -347,7 +531,7 @@ const updateObjectMethod = createInternalOrExternalMethod( * @param {string} bucket - Name of the bucket containing the object. * @param {string} key - Key of the object to delete. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise} Confirmation of deletion or null if deletion failed. + * @returns {Promise>} Confirmation of deletion or error if deletion failed. */ const deleteObjectMethod = createInternalOrExternalMethod( async ( @@ -355,19 +539,37 @@ const deleteObjectMethod = createInternalOrExternalMethod( bucket: string, key: string, options?: AzionClientOptions, - ): Promise => { + ): Promise> => { const internalClient = new InternalStorageClient(token, options?.debug); internalClient.name = bucket; - return internalClient.deleteObject({ key }); + const internalResult = await internalClient.deleteObject({ key }); + if (internalResult.data) { + return { + data: internalResult.data, + }; + } + return { + error: internalResult.error, + }; }, async ( token: string, bucket: string, key: string, options?: AzionClientOptions, - ): Promise => { + ): Promise> => { const apiResponse = await deleteObject(resolveToken(token), bucket, key, resolveDebug(options?.debug)); - return apiResponse ? { key: key, state: apiResponse.state } : null; + if (apiResponse.data) { + return { + data: { + key, + state: apiResponse.state, + }, + }; + } + return { + error: apiResponse.error, + }; }, ); @@ -378,14 +580,14 @@ const deleteObjectMethod = createInternalOrExternalMethod( * @param {string} params.name - Name of the new bucket. * @param {string} params.edge_access - Edge access configuration for the bucket. * @param {AzionClientOptions} [params.options] - Client options including debug mode. - * @returns {Promise} The created bucket or null if creation failed. + * @returns {Promise} The created bucket or error message. * * @example - * const newBucket = await createBucket({ name: 'my-new-bucket', edge_access: 'public', options: { debug: true } }); - * if (newBucket) { - * console.log(`Bucket created with name: ${newBucket.name}`); + * const { data, error } = await createBucket({ name: 'my-new-bucket', edge_access: 'public', options: { debug: true } }); + * if (data) { + * console.log(`Bucket created with name: ${data.name}`); * } else { - * console.error('Failed to create bucket'); + * console.error('Failed to create bucket', error); * } */ const createBucketWrapper = ({ @@ -396,7 +598,7 @@ const createBucketWrapper = ({ name: string; edge_access: string; options?: AzionClientOptions; -}): Promise => +}): Promise> => createBucketMethod(resolveToken(), name, edge_access, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -405,14 +607,14 @@ const createBucketWrapper = ({ * @param {Object} params - Parameters for deleting a bucket. * @param {string} params.name - Name of the bucket to delete. * @param {AzionClientOptions} [params.options] - Client options including debug mode. - * @returns {Promise} Confirmation of deletion or null if deletion failed. + * @returns {Promise>} Confirmation of deletion or error message. * * @example - * const result = await deleteBucket({ name: 'my-bucket', options: { debug: true } }); - * if (result) { - * console.log(`Bucket ${result.name} deleted successfully`); + * const { data, error } = await deleteBucket({ name: 'my-bucket', options: { debug: true } }); + * if (data) { + * console.log(`Bucket ${data.name} deleted successfully`); * } else { - * console.error('Failed to delete bucket'); + * console.error('Failed to delete bucket', error); * } */ const deleteBucketWrapper = ({ @@ -421,7 +623,7 @@ const deleteBucketWrapper = ({ }: { name: string; options?: AzionClientOptions; -}): Promise => +}): Promise> => deleteBucketMethod(resolveToken(), name, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -433,11 +635,11 @@ const deleteBucketWrapper = ({ * @returns {Promise} Array of bucket objects or null if retrieval failed. * * @example - * const buckets = await getBuckets({ params: { limit: 10, offset: 0 }, options: { debug: true } }); + * const { data: buckets, error } = await getBuckets({ params: { limit: 10, offset: 0 }, options: { debug: true } }); * if (buckets) { * console.log(`Retrieved ${buckets.length} buckets`); * } else { - * console.error('Failed to retrieve buckets'); + * console.error('Failed to retrieve buckets', error); * } */ const getBucketsWrapper = ({ @@ -446,7 +648,7 @@ const getBucketsWrapper = ({ }: { params?: AzionBucketCollectionParams; options?: AzionClientOptions; -} = {}): Promise => +}): Promise> => getBucketsMethod(resolveToken(), params, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -455,14 +657,14 @@ const getBucketsWrapper = ({ * @param {Object} params - Parameters for retrieving a bucket. * @param {string} params.name - Name of the bucket to retrieve. * @param {AzionClientOptions} [params.options] - Client options including debug mode. - * @returns {Promise} The retrieved bucket or null if not found. + * @returns {Promise>} The retrieved bucket or error message. * * @example - * const bucket = await getBucket({ name: 'my-bucket', options: { debug: true } }); + * const { data: bucket, error } = await getBucket({ name: 'my-bucket', options: { debug: true } }); * if (bucket) { * console.log(`Retrieved bucket: ${bucket.name}`); * } else { - * console.error('Bucket not found'); + * console.error('Bucket not found', error); * } */ const getBucketWrapper = ({ @@ -471,7 +673,7 @@ const getBucketWrapper = ({ }: { name: string; options?: AzionClientOptions; -}): Promise => +}): Promise> => getBucketMethod(resolveToken(), name, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -481,14 +683,14 @@ const getBucketWrapper = ({ * @param {string} params.name - Name of the bucket to update. * @param {string} params.edge_access - New edge access configuration for the bucket. * @param {AzionClientOptions} [params.options] - Client options including debug mode. - * @returns {Promise} The updated bucket or null if update failed. + * @returns {Promise>} The updated bucket or error message. * * @example - * const updatedBucket = await updateBucket({ name: 'my-bucket', edge_access: 'private', options: { debug: true } }); + * const { data: updatedBucket, error } = await updateBucket({ name: 'my-bucket', edge_access: 'private', options: { debug: true } }); * if (updatedBucket) { * console.log(`Bucket updated: ${updatedBucket.name}`); * } else { - * console.error('Failed to update bucket'); + * console.error('Failed to update bucket', error); * } */ const updateBucketWrapper = ({ @@ -499,7 +701,7 @@ const updateBucketWrapper = ({ name: string; edge_access: string; options?: AzionClientOptions; -}): Promise => +}): Promise> => updateBucketMethod(resolveToken(), name, edge_access, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -513,11 +715,11 @@ const updateBucketWrapper = ({ * @returns {Promise} Array of bucket objects or null if retrieval failed. * * @example - * const objects = await getObjects({ bucket: 'my-bucket', params: { max_object_count: 50 }, options: { debug: true } }); + * const { data: objects, error } = await getObjects({ bucket: 'my-bucket', params: { max_object_count: 50 }, options: { debug: true } }); * if (objects) { * console.log(`Retrieved ${objects.length} objects from the bucket`); * } else { - * console.error('Failed to retrieve objects'); + * console.error('Failed to retrieve objects', error); * } */ const getObjectsWrapper = ({ @@ -528,7 +730,7 @@ const getObjectsWrapper = ({ bucket: string; params?: AzionObjectCollectionParams; options?: AzionClientOptions; -}): Promise => getObjectsMethod(resolveToken(), bucket, params, options); +}): Promise> => getObjectsMethod(resolveToken(), bucket, params, options); /** * Creates a new object in a specific bucket. @@ -538,15 +740,15 @@ const getObjectsWrapper = ({ * @param {string} params.key - Key (name) of the object to create. * @param {string} params.content - Content of the content to upload. * @param {AzionClientOptions} [params.options] - Client options including debug mode. - * @returns {Promise} The created object or null if creation failed. + * @returns {Promise>} The created object or error message * * @example - * const newObject = await createObject({ bucket: 'my-bucket', key: 'new-content.txt', content: 'content content', options: { debug: true } }); + * const { data: newObject, error } = await createObject({ bucket: 'my-bucket', key: 'new-content.txt', content: 'content content', options: { debug: true } }); * if (newObject) { * console.log(`Object created with key: ${newObject.key}`); * console.log(`Object content: ${newObject.content}`); * } else { - * console.error('Failed to create object'); + * console.error('Failed to create object', error); * } */ const createObjectWrapper = ({ @@ -559,7 +761,7 @@ const createObjectWrapper = ({ key: string; content: string; options?: AzionClientOptions; -}): Promise => +}): Promise> => createObjectMethod(resolveToken(), bucket, key, content, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -569,14 +771,14 @@ const createObjectWrapper = ({ * @param {string} params.bucket - Name of the bucket containing the object. * @param {string} params.key - Key of the object to retrieve. * @param {AzionClientOptions} [params.options] - Client options including debug mode. - * @returns {Promise} The retrieved object or null if not found. + * @returns {Promise>} The retrieved object or error message. * * @example - * const object = await getObjectByKey({ bucket: 'my-bucket', key: 'content.txt', options: { debug: true } }); + * const { data: object, error } = await getObjectByKey({ bucket: 'my-bucket', key: 'content.txt', options: { debug: true } }); * if (object) { * console.log(`Retrieved object: ${object.key}`); * } else { - * console.error('Object not found'); + * console.error('Object not found', error); * } */ const getObjectByKeyWrapper = ({ @@ -587,7 +789,7 @@ const getObjectByKeyWrapper = ({ bucket: string; key: string; options?: AzionClientOptions; -}): Promise => +}): Promise> => getObjectByKeyMethod(resolveToken(), bucket, key, { ...options, debug: resolveDebug(options?.debug), @@ -601,15 +803,15 @@ const getObjectByKeyWrapper = ({ * @param {string} params.key - Key of the object to update. * @param {string} params.content - New content of the content. * @param {AzionClientOptions} [params.options] - Client options including debug mode. - * @returns {Promise} The updated object or null if update failed. + * @returns {Promise>} The updated object or error message. * * @example - * const updatedObject = await updateObject({ bucket: 'my-bucket', key: 'content.txt', content: 'Updated content', options: { debug: true } }); + * const { data: updatedObject, error } = await updateObject({ bucket: 'my-bucket', key: 'content.txt', content: 'Updated content', options: { debug: true } }); * if (updatedObject) { * console.log(`Object updated: ${updatedObject.key}`); * console.log(`New content: ${updatedObject.content}`); * } else { - * console.error('Failed to update object'); + * console.error('Failed to update object', error); * } */ const updateObjectWrapper = ({ @@ -622,7 +824,7 @@ const updateObjectWrapper = ({ key: string; content: string; options?: AzionClientOptions; -}): Promise => +}): Promise> => updateObjectMethod(resolveToken(), bucket, key, content, { ...options, debug: resolveDebug(options?.debug), @@ -635,14 +837,14 @@ const updateObjectWrapper = ({ * @param {string} params.bucket - Name of the bucket containing the object. * @param {string} params.key - Key of the object to delete. * @param {AzionClientOptions} [params.options] - Client options including debug mode. - * @returns {Promise} Confirmation of deletion or null if deletion failed. + * @returns {Promise>} Confirmation of deletion or error if deletion failed. * * @example - * const result = await deleteObject({ bucket: 'my-bucket', key: 'content.txt', options: { debug: true } }); + * const { data: result, error } = await deleteObject({ bucket: 'my-bucket', key: 'content.txt', options: { debug: true } }); * if (result) { * console.log(`Object ${result.key} deleted successfully`); * } else { - * console.error('Failed to delete object'); + * console.error('Failed to delete object', error); * } */ const deleteObjectWrapper = ({ @@ -653,7 +855,7 @@ const deleteObjectWrapper = ({ bucket: string; key: string; options?: AzionClientOptions; -}): Promise => +}): Promise> => deleteObjectMethod(resolveToken(), bucket, key, { ...options, debug: resolveDebug(options?.debug), @@ -669,10 +871,10 @@ const deleteObjectWrapper = ({ * const storageClient = createClient({ token: 'your-api-token', options: { debug: true } }); * * // Create a new bucket - * const newBucket = await storageClient.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); + * const { data, error } = await storageClient.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); * * // Get all buckets - * const allBuckets = await storageClient.getBuckets({ params: { page: 1, page_size: 10 } }); + * const { data: allBuckets } = await storageClient.getBuckets({ params: { page: 1, page_size: 10 } }); * * // Delete a bucket * const deletedBucket = await storageClient.deleteBucket({ name: 'my-bucket' }); @@ -684,17 +886,27 @@ const client: CreateAzionStorageClient = ( const debugValue = resolveDebug(config?.options?.debug); const client: AzionStorageClient = { - getBuckets: ( - params: { params?: AzionBucketCollectionParams; options?: AzionClientOptions } = {}, - ): Promise => - getBucketsMethod(tokenValue, params.params, { ...config, ...params.options, debug: debugValue }), - createBucket: ({ name, edge_access }: { name: string; edge_access: string }): Promise => + getBuckets: (params?: { params?: AzionBucketCollectionParams }): Promise> => + getBucketsMethod(tokenValue, params?.params, { ...config, debug: debugValue }), + createBucket: ({ + name, + edge_access, + }: { + name: string; + edge_access: string; + }): Promise> => createBucketMethod(tokenValue, name, edge_access, { ...config, debug: debugValue }), - updateBucket: ({ name, edge_access }: { name: string; edge_access: string }): Promise => + updateBucket: ({ + name, + edge_access, + }: { + name: string; + edge_access: string; + }): Promise> => updateBucketMethod(tokenValue, name, edge_access, { ...config, debug: debugValue }), - deleteBucket: ({ name }: { name: string }): Promise => + deleteBucket: ({ name }: { name: string }): Promise> => deleteBucketMethod(tokenValue, name, { ...config, debug: debugValue }), - getBucket: ({ name }: { name: string }): Promise => + getBucket: ({ name }: { name: string }): Promise> => getBucketMethod(tokenValue, name, { ...config, debug: debugValue }), } as const; diff --git a/packages/storage/src/services/api/index.ts b/packages/storage/src/services/api/index.ts index 1cd8785..d3bfda4 100644 --- a/packages/storage/src/services/api/index.ts +++ b/packages/storage/src/services/api/index.ts @@ -4,6 +4,7 @@ import { ApiDeleteBucketResponse, ApiDeleteObjectResponse, ApiEditBucketResponse, + ApiError, ApiListBucketsParams, ApiListBucketsResponse, ApiListObjectsParams, @@ -15,6 +16,21 @@ const BASE_URL = ? 'https://stage-api.azion.com/v4/storage/buckets' : 'https://api.azion.com/v4/storage/buckets'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const handleApiError = (fields: string[], data: any, operation: string) => { + let error = { message: 'Error unknown', operation: operation }; + fields.forEach((field: string) => { + if (data[field]) { + const message = Array.isArray(data[field]) ? data[field].join(', ') : data[field]; + error = { + message: message, + operation: operation, + }; + } + }); + return error; +}; + /** * Retrieves a list of buckets with optional filtering and pagination. * @@ -36,6 +52,12 @@ const getBuckets = async ( headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, }); const data = await response.json(); + if (!data.results) { + data.error = handleApiError(['detail'], data, 'get all buckets'); + return { + error: data.error ?? JSON.stringify(data), + }; + } if (debug) console.log('Response:', data); return data; } catch (error) { @@ -66,6 +88,13 @@ const postBucket = async ( body: JSON.stringify({ name, edge_access }), }); const data = await response.json(); + + if (!data?.state) { + data.error = handleApiError(['name', 'edge_access', 'detail'], data, 'create bucket'); + return { + error: data.error ?? JSON.stringify(data), + }; + } if (debug) console.log('Response:', data); return data; } catch (error) { @@ -96,6 +125,12 @@ const patchBucket = async ( body: JSON.stringify({ edge_access }), }); const data = await response.json(); + if (!data?.state) { + data.error = handleApiError(['name', 'edge_access', 'detail'], data, 'update bucket'); + return { + error: data.error ?? JSON.stringify(data), + }; + } if (debug) console.log('Response:', data); return data; } catch (error) { @@ -119,7 +154,13 @@ const deleteBucket = async (token: string, name: string, debug?: boolean): Promi headers: { Accept: 'application/json', Authorization: `Token ${token}` }, }); const data = await response.json(); - if (debug) console.log('Response:', data); + if (!data?.state) { + data.error = handleApiError(['detail'], data, 'delete bucket'); + return { + error: data.error ?? JSON.stringify(data), + }; + } + if (debug) console.log('Response Delete Bucket:', data); return data; } catch (error) { if (debug) console.error('Error deleting bucket:', error); @@ -150,6 +191,12 @@ const getObjects = async ( headers: { Accept: 'application/json', Authorization: `Token ${token}` }, }); const data = await response.json(); + if (!data.results) { + data.error = handleApiError(['detail'], data, 'get all objects'); + return { + error: data.error ?? JSON.stringify(data), + }; + } if (debug) console.log('Response:', data); return data; } catch (error) { @@ -186,6 +233,12 @@ const postObject = async ( body: file, }); const data = await response.json(); + if (!data?.state) { + data.error = handleApiError(['detail'], data, 'create object'); + return { + error: data.error ?? JSON.stringify(data), + }; + } if (debug) console.log('Response:', data); return data; } catch (error) { @@ -201,17 +254,31 @@ const postObject = async ( * @param {string} bucketName - Name of the bucket. * @param {string} key - Key of the object to retrieve. * @param {boolean} [debug] - Enable debug mode for detailed logging. - * @returns {Promise} The content of the object or an error if retrieval failed. + * @returns {Promise<{ data?: string; error?: ApiError }>} The content of the object or an error if retrieval failed. */ -const getObjectByKey = async (token: string, bucketName: string, key: string, debug?: boolean): Promise => { +const getObjectByKey = async ( + token: string, + bucketName: string, + key: string, + debug?: boolean, +): Promise<{ data?: string; error?: ApiError }> => { try { const response = await fetch(`${BASE_URL}/${bucketName}/objects/${key}`, { method: 'GET', headers: { Accept: 'application/json', Authorization: `Token ${token}` }, }); + if (response.headers.get('content-type') === 'application/json') { + const data = await response.json(); + const error = handleApiError(['detail'], data, 'get all objects'); + return { + error: error ?? JSON.stringify(data), + }; + } const data = await response.text(); if (debug) console.log('Response:', data); - return data; + return { + data, + }; } catch (error) { if (debug) console.error('Error getting object by name:', error); throw error; @@ -275,6 +342,12 @@ const deleteObject = async ( headers: { Accept: 'application/json', Authorization: `Token ${token}` }, }); const data = await response.json(); + if (!data?.state) { + data.error = handleApiError(['detail'], data, 'delete object'); + return { + error: data.error ?? JSON.stringify(data), + }; + } if (debug) console.log('Response:', data); return data; } catch (error) { diff --git a/packages/storage/src/services/api/types.d.ts b/packages/storage/src/services/api/types.d.ts index 281e122..0e9bb7d 100644 --- a/packages/storage/src/services/api/types.d.ts +++ b/packages/storage/src/services/api/types.d.ts @@ -1,49 +1,75 @@ +export type ApiBucketObject = { + key: string; + last_modified: string; + size: number; +}; + export interface ApiBucket { name: string; edge_access: string; } +export type ApiError = { + message: string; + operation: string; +}; + +export interface ApiGetBucket { + data?: { + name: string; + edge_access: string; + }; + error?: ApiError; +} + export interface ApiListBucketsResponse { - links: { + links?: { next: string | null; previous: string | null; }; - count: number; - results: ApiBucket[]; + count?: number; + results?: ApiBucket[]; + error?: ApiError; } export interface ApiCreateBucketResponse { - state: 'executed' | 'pending'; - data: ApiBucket; + state?: 'executed' | 'pending'; + data?: ApiBucket; + error?: ApiError; } export interface ApiEditBucketResponse { - state: 'executed' | 'pending'; - data: ApiBucket; + state?: 'executed' | 'pending'; + data?: ApiBucket; + error?: ApiError; } export interface ApiDeleteBucketResponse { - state: 'executed' | 'pending'; - data: Bucket; + state?: 'executed' | 'pending'; + data?: ApiBucket; + error?: ApiError; } export interface ApiListObjectsResponse { - continuation_token: string | null; - results: BucketObject[]; + continuation_token?: string | null; + results?: ApiBucketObject[]; + error?: ApiError; } export interface ApiCreateObjectResponse { - state: 'executed' | 'pending'; - data: { + state?: 'executed' | 'pending'; + data?: { object_key: string; }; + error?: ApiError; } export interface ApiDeleteObjectResponse { - state: 'executed' | 'pending'; - data: { + state?: 'executed' | 'pending'; + data?: { object_key: string; }; + error?: ApiError; } export interface ApiUpdateObjectResponse { diff --git a/packages/storage/src/services/runtime/index.ts b/packages/storage/src/services/runtime/index.ts index b33cd01..a1db782 100644 --- a/packages/storage/src/services/runtime/index.ts +++ b/packages/storage/src/services/runtime/index.ts @@ -1,5 +1,12 @@ import { Azion } from 'azion/types'; -import { AzionBucket, AzionBucketObject, AzionDeletedBucketObject, AzionObjectCollectionParams } from '../../types'; +import { + AzionBucket, + AzionBucketObject, + AzionBucketObjects, + AzionDeletedBucketObject, + AzionObjectCollectionParams, + AzionStorageResponse, +} from '../../types'; import { removeLeadingSlash, retryWithBackoff } from '../../utils/index'; export const isInternalStorageAvailable = (): boolean => { @@ -69,9 +76,13 @@ export class InternalStorageClient implements AzionBucket { * * @param {Object} params - Parameters for object collection. * @param {AzionObjectCollectionParams} [params.params] - Parameters for object collection. - * @returns {Promise} The list of objects or null if an error occurs. + * @returns {Promise>} The list of objects or error message. */ - async getObjects({ params }: { params?: AzionObjectCollectionParams }): Promise { + async getObjects({ + params, + }: { + params?: AzionObjectCollectionParams; + }): Promise> { this.initializeStorage(this.name); try { const objectList = await retryWithBackoff(() => this.storage!.list()); @@ -85,10 +96,20 @@ export class InternalStorageClient implements AzionBucket { }; }), ); - return objects; + return { + data: { + objects, + count: objects.length, + }, + }; } catch (error) { if (this.debug) console.error('Error getting objects:', error); - return null; + return { + error: { + message: (error as Error)?.message ?? 'Error getting objects', + operation: 'getObjects', + }, + }; } } @@ -99,9 +120,9 @@ export class InternalStorageClient implements AzionBucket { * * @param {Object} params - Parameters for retrieving an object. * @param {string} params.key - The key of the object to retrieve. - * @returns {Promise} The object or null if an error occurs. + * @returns {Promise>} The object or null if an error occurs. */ - async getObjectByKey({ key }: { key: string }): Promise { + async getObjectByKey({ key }: { key: string }): Promise> { this.initializeStorage(this.name); try { const storageObject = await retryWithBackoff(() => this.storage!.get(key)); @@ -109,15 +130,22 @@ export class InternalStorageClient implements AzionBucket { const decoder = new TextDecoder(); const content = decoder.decode(arrayBuffer); return { - state: 'executed-runtime', - key: removeLeadingSlash(key), - size: storageObject.contentLength, - content: content, - content_type: storageObject.metadata.get('content-type'), + data: { + state: 'executed-runtime', + key: removeLeadingSlash(key), + size: storageObject.contentLength, + content: content, + content_type: storageObject.metadata.get('content-type'), + }, }; } catch (error) { if (this.debug) console.error('Error getting object by key:', error); - return null; + return { + error: { + message: (error as Error)?.message ?? 'Error getting object by key', + operation: 'getObjectByKey', + }, + }; } } @@ -130,7 +158,7 @@ export class InternalStorageClient implements AzionBucket { * @param {string} params.key - The key of the object to create. * @param {string} params.content - The content of the object. * @param {{ content_type?: string }} [params.options] - Optional metadata for the object. - * @returns {Promise} The created object or null if an error occurs. + * @returns {Promise>} The created object or error message. */ async createObject({ key, @@ -140,7 +168,7 @@ export class InternalStorageClient implements AzionBucket { key: string; content: string; options?: { content_type?: string }; - }): Promise { + }): Promise> { this.initializeStorage(this.name); try { const contentBuffer = new TextEncoder().encode(content); @@ -150,15 +178,22 @@ export class InternalStorageClient implements AzionBucket { }), ); return { - state: 'executed-runtime', - key: removeLeadingSlash(key), - size: contentBuffer.byteLength, - content_type: options?.content_type, - content: content, + data: { + state: 'executed-runtime', + key: removeLeadingSlash(key), + size: contentBuffer.byteLength, + content_type: options?.content_type, + content: content, + }, }; } catch (error) { if (this.debug) console.error('Error creating object:', error); - return null; + return { + error: { + message: (error as Error)?.message ?? 'Error creating object', + operation: 'createObject', + }, + }; } } @@ -171,7 +206,7 @@ export class InternalStorageClient implements AzionBucket { * @param {string} params.key - The key of the object to update. * @param {string} params.content - The new content of the object. * @param {{ content_type?: string }} [params.options] - Optional metadata for the object. - * @returns {Promise} The updated object or null if an error occurs. + * @returns {Promise>} The updated object or error message. */ async updateObject({ key, @@ -181,7 +216,7 @@ export class InternalStorageClient implements AzionBucket { key: string; content: string; options?: { content_type?: string }; - }): Promise { + }): Promise> { return this.createObject({ key, content, options }); } @@ -192,16 +227,21 @@ export class InternalStorageClient implements AzionBucket { * * @param {Object} params - Parameters for deleting an object. * @param {string} params.key - The key of the object to delete. - * @returns {Promise} Confirmation of deletion or null if an error occurs. + * @returns {Promise>} Confirmation of deletion or error if an error occurs. */ - async deleteObject({ key }: { key: string }): Promise { + async deleteObject({ key }: { key: string }): Promise> { this.initializeStorage(this.name); try { await retryWithBackoff(() => this.storage!.delete(key)); - return { key: removeLeadingSlash(key), state: 'executed-runtime' }; + return { data: { key: removeLeadingSlash(key), state: 'executed-runtime' } }; } catch (error) { if (this.debug) console.error('Error deleting object:', error); - return null; + return { + error: { + message: (error as Error)?.message ?? 'Error deleting object', + operation: 'deleteObject', + }, + }; } } } diff --git a/packages/storage/src/types.ts b/packages/storage/src/types.ts index a48cb47..fbe4a81 100644 --- a/packages/storage/src/types.ts +++ b/packages/storage/src/types.ts @@ -1,4 +1,13 @@ // eslint-disable-next-line @typescript-eslint/no-namespace + +export type AzionStorageResponse = { + data?: T; + error?: { + message: string; + operation: string; + }; +}; + /** * Represents an Azion storage bucket with methods to interact with objects. * @@ -18,24 +27,24 @@ export interface AzionBucket { * * @param {Object} params - Parameters for retrieving objects. * @param {AzionObjectCollectionParams} params.params - Options for filtering and pagination. - * @returns {Promise} A promise that resolves to an array of bucket objects or null. + * @returns {Promise>} A promise that resolves to an array of bucket objects or error message. * * @example - * const objects = await bucket.getObjects({ params: { max_object_count: 100 } }); + * const { data: objects, error } = await bucket.getObjects({ params: { max_object_count: 100 } }); */ - getObjects: (params: { params: AzionObjectCollectionParams }) => Promise; + getObjects: (params: { params: AzionObjectCollectionParams }) => Promise>; /** * Retrieves a specific object from the bucket by its key. * * @param {Object} params - Parameters for retrieving the object. * @param {string} params.key - The key of the object to retrieve. - * @returns {Promise} A promise that resolves to the bucket object or null if not found. + * @returns {Promise>} A promise that resolves to the bucket object or error message. * * @example - * const object = await bucket.getObjectByKey({ key: 'example.txt' }); + * const { data: object } = await bucket.getObjectByKey({ key: 'example.txt' }); */ - getObjectByKey: (params: { key: string }) => Promise; + getObjectByKey: (params: { key: string }) => Promise>; /** * Creates a new object in the bucket. @@ -45,10 +54,10 @@ export interface AzionBucket { * @param {string} params.content - The content of the new object. * @param {Object} [params.options] - Additional options for the object. * @param {string} [params.options.content_type] - The content type of the object. - * @returns {Promise} A promise that resolves to the created bucket object or null if creation failed. + * @returns {Promise>} A promise that resolves to the created bucket object or error message. * * @example - * const newObject = await bucket.createObject({ + * const { data: newObject } = await bucket.createObject({ * key: 'new-file.txt', * content: 'Hello, World!', * options: { content_type: 'text/plain' } @@ -58,7 +67,7 @@ export interface AzionBucket { key: string; content: string; options?: { content_type?: string }; - }) => Promise; + }) => Promise>; /** * Updates an existing object in the bucket. @@ -68,10 +77,10 @@ export interface AzionBucket { * @param {string} params.content - The new content for the object. * @param {Object} [params.options] - Additional options for the object. * @param {string} [params.options.content_type] - The new content type for the object. - * @returns {Promise} A promise that resolves to the updated bucket object or null if update failed. + * @returns {Promise>} A promise that resolves to the updated bucket object or error message. * * @example - * const updatedObject = await bucket.updateObject({ + * const { data: updatedObject } = await bucket.updateObject({ * key: 'existing-file.txt', * content: 'Updated content', * options: { content_type: 'text/plain' } @@ -81,19 +90,19 @@ export interface AzionBucket { key: string; content: string; options?: { content_type?: string }; - }) => Promise; + }) => Promise>; /** * Deletes an object from the bucket. * * @param {Object} params - Parameters for deleting the object. * @param {string} params.key - The key of the object to delete. - * @returns {Promise} A promise that resolves to the deleted bucket object or null if deletion failed. + * @returns {Promise>} A promise that resolves to the deleted bucket object or error if deletion failed. * * @example - * const deletedObject = await bucket.deleteObject({ key: 'file-to-delete.txt' }); + * const { data: deletedObject, error } = await bucket.deleteObject({ key: 'file-to-delete.txt' }); */ - deleteObject: (params: { key: string }) => Promise; + deleteObject: (params: { key: string }) => Promise>; } export interface AzionBucketObject { @@ -105,6 +114,11 @@ export interface AzionBucketObject { content?: string; } +export interface AzionBucketObjects { + objects: AzionBucketObject[]; + count: number; +} + export interface AzionDeletedBucketObject { key: string; state?: 'executed' | 'executed-runtime' | 'pending'; @@ -115,43 +129,50 @@ export interface AzionDeletedBucket { state?: 'executed' | 'executed-runtime' | 'pending'; } +export interface AzionBuckets { + buckets: AzionBucket[]; + count: number; +} + export interface AzionStorageClient { /** * Retrieves a list of buckets with optional filtering and pagination. * @param {Object} params - Parameters for retrieving buckets. * @param {AzionBucketCollectionParams} [params.params] - Optional parameters for filtering and pagination. - * @returns {Promise} Array of buckets or null if retrieval failed. + * @returns {Promise>} Array of buckets or error message. */ - getBuckets: (params?: { params?: AzionBucketCollectionParams }) => Promise; + getBuckets: (params?: { params?: AzionBucketCollectionParams }) => Promise>; /** * Creates a new bucket. * @param {Object} params - Parameters for creating a bucket. * @param {string} params.name - Name of the new bucket. * @param {string} params.edge_access - Edge access configuration for the bucket. - * @returns {Promise} The created bucket or null if creation failed. + * @returns {Promise} The created bucket or error message. */ - createBucket: (params: { name: string; edge_access: string }) => Promise; + createBucket: (params: { name: string; edge_access: string }) => Promise>; + /** - * Deletes a bucket by its name. - * @param {Object} params - Parameters for deleting a bucket. - * @param {string} params.name - Name of the bucket to delete. - * @returns {Promise} Confirmation of deletion or null if deletion failed. + * Updates a bucket by its name. + * @param {Object} params - Parameters for updating a bucket. + * @param {string} params.name - Name of the bucket to update. + * @param {string} params.edge_access - New edge access configuration for the bucket. + * @returns {Promise>} The updated bucket or error message. */ - updateBucket: (params: { name: string; edge_access: string }) => Promise; + updateBucket: (params: { name: string; edge_access: string }) => Promise>; /** * Deletes a bucket by its name. * @param {Object} params - Parameters for deleting a bucket. * @param {string} params.name - Name of the bucket to delete. - * @returns {Promise} Confirmation of deletion or null if deletion failed. + * @returns {Promise>} Confirmation of deletion or error message. */ - deleteBucket: (params: { name: string }) => Promise; + deleteBucket: (params: { name: string }) => Promise>; /** * Retrieves a bucket by its name. * @param {Object} params - Parameters for retrieving a bucket. * @param {string} params.name - Name of the bucket to retrieve. - * @returns {Promise} The retrieved bucket or null if not found. + * @returns {Promise>} The retrieved bucket or error message. */ - getBucket: (params: { name: string }) => Promise; + getBucket: (params: { name: string }) => Promise>; } export type AzionBucketCollectionParams = { diff --git a/packages/storage/src/utils/index.ts b/packages/storage/src/utils/index.ts index 1fe0a02..26193db 100644 --- a/packages/storage/src/utils/index.ts +++ b/packages/storage/src/utils/index.ts @@ -1,5 +1,5 @@ import { getBuckets } from '../services/api/index'; -import { ApiBucket } from '../services/api/types'; +import { ApiGetBucket } from '../services/api/types'; import { AzionClientOptions } from '../types'; /** @@ -57,18 +57,30 @@ export const retryWithBackoff = async (fn: () => Promise, delay: number = * @param {string} token The authentication token. * @param {string} name The name of the bucket to find. * @param {AzionClientOptions} [options] Optional client options. - * @returns {Promise} The bucket if found, or null if not found. + * @returns {Promise} The bucket if found, or error message. */ export const findBucketByName = async ( token: string, name: string, options?: AzionClientOptions, -): Promise => { +): Promise => { const PAGE_SIZE_TEMP = 1000000; const apiResponse = await getBuckets(token, { page_size: PAGE_SIZE_TEMP }, options?.debug ?? false); const buckets = apiResponse.results; - if (!buckets) return null; - + if (apiResponse.error) + return { + error: apiResponse.error, + }; + if (!buckets) { + return { + error: { + message: 'Failed to retrieve buckets.', + operation: 'getBuckets', + }, + }; + } const bucket = buckets.find((b) => b.name === name); - return bucket ?? null; + return { + data: bucket, + }; }; From 178718eca1f963c4f7edaf12fcf3421cfe7778c6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 5 Sep 2024 20:01:58 +0000 Subject: [PATCH 30/45] chore(release): 1.7.0-stage.9 [skip ci] ## [1.7.0-stage.9](https://github.com/aziontech/lib/compare/v1.7.0-stage.8...v1.7.0-stage.9) (2024-09-05) ### Features * updating function returns (#36) ([088bbd3](https://github.com/aziontech/lib/commit/088bbd359a9f33de73352a35db59601dee053285)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd7dc1a..8c40934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.7.0-stage.9](https://github.com/aziontech/lib/compare/v1.7.0-stage.8...v1.7.0-stage.9) (2024-09-05) + + +### Features + +* updating function returns (#36) ([088bbd3](https://github.com/aziontech/lib/commit/088bbd359a9f33de73352a35db59601dee053285)) + ## [1.7.0-stage.8](https://github.com/aziontech/lib/compare/v1.7.0-stage.7...v1.7.0-stage.8) (2024-09-03) diff --git a/package.json b/package.json index 51e386d..668108a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.7.0-stage.8", + "version": "1.7.0-stage.9", "description": "Azion Packages for Edge Computing.", "bin": { "azion": "./bin/azion" From e53c5b0908233484d976c2bb25df921bb9588b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 9 Sep 2024 19:03:58 -0300 Subject: [PATCH 31/45] feat: add package application (#37) * feat: application sdk * refactor: app sdk * refactor: types * refactor: structure * feat: edge application sdk * feat: export wrappers * chore: jsdoc * fix: exports * chore: fix jsdoc * refactor: errors + token and debug * refactor: app methods * refactor: error handler * fix: app wrapper type * fix: error message * refactor: pack name * refactor: optional param * fix: error feedback * fix: error message * chore: improve jsdocs --- package.json | 8 + packages/applications/package.json | 27 + .../applications/src/cache-settings/index.ts | 348 +++++ .../src/cache-settings/services/index.ts | 130 ++ .../src/cache-settings/services/types.ts | 98 ++ .../applications/src/cache-settings/types.ts | 10 + .../applications/src/device-groups/index.ts | 323 +++++ .../src/device-groups/services/index.ts | 178 +++ .../src/device-groups/services/types.ts | 49 + .../applications/src/device-groups/types.ts | 5 + .../src/functions-instances/index.ts | 344 +++++ .../src/functions-instances/services/index.ts | 185 +++ .../src/functions-instances/services/types.ts | 59 + .../src/functions-instances/types.ts | 10 + packages/applications/src/index.ts | 178 +++ .../applications/src/main-settings/index.ts | 1056 +++++++++++++++ .../src/main-settings/services/index.ts | 194 +++ .../src/main-settings/services/types.ts | 102 ++ .../applications/src/main-settings/types.ts | 24 + packages/applications/src/origins/index.ts | 270 ++++ .../src/origins/services/index.ts | 179 +++ .../src/origins/services/types.ts | 96 ++ packages/applications/src/origins/types.ts | 5 + .../applications/src/rules-engine/index.ts | 367 +++++ .../src/rules-engine/services/index.ts | 178 +++ .../src/rules-engine/services/types.ts | 136 ++ .../applications/src/rules-engine/types.ts | 5 + packages/applications/src/types.ts | 1181 +++++++++++++++++ packages/applications/src/utils.ts | 56 + packages/applications/tsconfig.json | 18 + packages/client/src/index.ts | 13 +- packages/client/src/types.ts | 81 +- packages/client/tsconfig.json | 3 +- packages/config/README.md | 6 +- packages/config/src/index.ts | 4 +- packages/config/src/types.ts | 2 +- 36 files changed, 5915 insertions(+), 13 deletions(-) create mode 100644 packages/applications/package.json create mode 100644 packages/applications/src/cache-settings/index.ts create mode 100644 packages/applications/src/cache-settings/services/index.ts create mode 100644 packages/applications/src/cache-settings/services/types.ts create mode 100644 packages/applications/src/cache-settings/types.ts create mode 100644 packages/applications/src/device-groups/index.ts create mode 100644 packages/applications/src/device-groups/services/index.ts create mode 100644 packages/applications/src/device-groups/services/types.ts create mode 100644 packages/applications/src/device-groups/types.ts create mode 100644 packages/applications/src/functions-instances/index.ts create mode 100644 packages/applications/src/functions-instances/services/index.ts create mode 100644 packages/applications/src/functions-instances/services/types.ts create mode 100644 packages/applications/src/functions-instances/types.ts create mode 100644 packages/applications/src/index.ts create mode 100644 packages/applications/src/main-settings/index.ts create mode 100644 packages/applications/src/main-settings/services/index.ts create mode 100644 packages/applications/src/main-settings/services/types.ts create mode 100644 packages/applications/src/main-settings/types.ts create mode 100644 packages/applications/src/origins/index.ts create mode 100644 packages/applications/src/origins/services/index.ts create mode 100644 packages/applications/src/origins/services/types.ts create mode 100644 packages/applications/src/origins/types.ts create mode 100644 packages/applications/src/rules-engine/index.ts create mode 100644 packages/applications/src/rules-engine/services/index.ts create mode 100644 packages/applications/src/rules-engine/services/types.ts create mode 100644 packages/applications/src/rules-engine/types.ts create mode 100644 packages/applications/src/types.ts create mode 100644 packages/applications/src/utils.ts create mode 100644 packages/applications/tsconfig.json diff --git a/package.json b/package.json index 668108a..28de2eb 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,11 @@ "import": "./packages/client/dist/index.mjs", "types": "./packages/client/dist/index.d.ts" }, + "./applications": { + "require": "./packages/applications/dist/index.js", + "import": "./packages/applications/dist/index.mjs", + "types": "./packages/applications/dist/index.d.ts" + }, "./types": { "require": "./packages/types/dist/index.js", "import": "./packages/types/dist/index.mjs", @@ -134,6 +139,9 @@ }, "typesVersions": { "*": { + "application": [ + "./packages/application/dist/index.d.ts" + ], "client": [ "./packages/client/dist/index.d.ts" ], diff --git a/packages/applications/package.json b/packages/applications/package.json new file mode 100644 index 0000000..1be5188 --- /dev/null +++ b/packages/applications/package.json @@ -0,0 +1,27 @@ +{ + "name": "@lib/application", + "version": "1.0.0", + "description": "", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "scripts": { + "compile": "tsup --config ../../tsup.config.json", + "lint": "eslint .", + "lint:fix": "eslint --fix .", + "prettier": "prettier --write ." + }, + "types": "./dist/index.d.ts", + "exports": { + ".": { + "require": "./dist/index.js", + "import": "./dist/index.mjs", + "types": "./dist/index.d.ts" + } + }, + "author": "aziontech", + "license": "MIT", + "files": [ + "dist", + "package.json" + ] +} diff --git a/packages/applications/src/cache-settings/index.ts b/packages/applications/src/cache-settings/index.ts new file mode 100644 index 0000000..93a7138 --- /dev/null +++ b/packages/applications/src/cache-settings/index.ts @@ -0,0 +1,348 @@ +import { AzionApplicationCollectionResponse, AzionApplicationResponse, AzionClientOptions } from '../types'; +import { resolveDebug, resolveToken } from '../utils'; +import { + deleteCacheSetting, + getCacheSetting, + getCacheSettings, + patchCacheSetting, + postCacheSetting, +} from './services/index'; +import { ApiBaseCacheSettingPayload, ApiListCacheSettingsParams, ApiUpdateCacheSettingPayload } from './services/types'; +import { AzionCacheSetting } from './types'; + +/** + * Creates a new cache setting for a specific application. + * + * @param {Object} params - Parameters for creating a cache setting. + * @param {number} params.applicationId - Application ID. + * @param {ApiBaseCacheSettingPayload} params.data - Data for the cache setting to be created. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The created cache setting or an error. + * + * @example + * const { error, data } = await createCacheSetting({ + * applicationId: 1234, + * data: { name: 'My Cache Setting', browser_cache_settings: 'override' }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to create cache setting:', error); + * } else { + * console.log('Cache setting created:', data.name); + * } + */ +export const createCacheSettingMethod = async ( + token: string, + Id: number, + cacheSettingData: ApiBaseCacheSettingPayload, + options?: AzionClientOptions, +): Promise> => { + try { + const apiResponse = await postCacheSetting(resolveToken(token), Id, cacheSettingData, resolveDebug(options?.debug)); + return { data: apiResponse.results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to create cache setting', + operation: 'create cache setting', + }, + }; + } +}; + +/** + * Retrieves a specific cache setting from an application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {number} cacheSettingId - Cache setting ID. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} The retrieved cache setting or an error. + */ +export const getCacheSettingMethod = async ( + token: string, + Id: number, + cacheSettingId: number, + options?: AzionClientOptions, +): Promise> => { + try { + const apiResponse = await getCacheSetting(resolveToken(token), Id, cacheSettingId, resolveDebug(options?.debug)); + return { data: apiResponse.results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to get cache setting', + operation: 'get cache setting', + }, + }; + } +}; + +/** + * Retrieves a list of cache settings for a specific application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {ApiListCacheSettingsParams} [params] - Parameters for listing cache settings. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} A collection of cache settings or an error. + */ +export const getCacheSettingsMethod = async ( + token: string, + Id: number, + params?: ApiListCacheSettingsParams, + options?: AzionClientOptions, +): Promise> => { + try { + const apiResponse = await getCacheSettings(resolveToken(token), Id, params, resolveDebug(options?.debug)); + + const results: AzionCacheSetting[] = apiResponse.results.map((setting) => ({ + ...setting, + })); + + return { + data: { + count: apiResponse.count, + total_pages: apiResponse.total_pages, + schema_version: apiResponse.schema_version, + links: apiResponse.links, + results, // results do tipo AzionCacheSetting[] + }, + }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to get cache settings', + operation: 'get cache settings', + }, + }; + } +}; + +/** + * Updates an existing cache setting for a specific application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {number} cacheSettingId - Cache setting ID to update. + * @param {ApiUpdateCacheSettingPayload} cacheSettingData - Updated data for the cache setting. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} The updated cache setting or an error. + */ +export const updateCacheSettingMethod = async ( + token: string, + Id: number, + cacheSettingId: number, + cacheSettingData: ApiUpdateCacheSettingPayload, + options?: AzionClientOptions, +): Promise> => { + try { + const apiResponse = await patchCacheSetting( + resolveToken(token), + Id, + cacheSettingId, + cacheSettingData, + resolveDebug(options?.debug), + ); + return { data: apiResponse.results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to update cache setting', + operation: 'update cache setting', + }, + }; + } +}; + +/** + * Deletes a specific cache setting from an application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {number} cacheSettingId - Cache setting ID to delete. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} A response indicating success or an error. + */ +export const deleteCacheSettingMethod = async ( + token: string, + Id: number, + cacheSettingId: number, + options?: AzionClientOptions, +): Promise> => { + try { + await deleteCacheSetting(resolveToken(token), Id, cacheSettingId, resolveDebug(options?.debug)); + return { data: undefined }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to delete cache setting', + operation: 'delete cache setting', + }, + }; + } +}; + +/** + * Wrapper function to create a new cache setting for a specific application. + * + * @param {Object} params - Parameters for creating a cache setting. + * @param {number} params.applicationId - Application ID. + * @param {ApiBaseCacheSettingPayload} params.data - Data for the cache setting to be created. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The created cache setting or an error. + * + * @example + * const { error, data } = await createCacheSetting({ + * applicationId: 1234, + * data: { name: 'My Cache Setting', browser_cache_settings: 'override' }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to create cache setting:', error); + * } else { + * console.log('Cache setting created:', data.name); + * } + */ +export const createCacheSettingWrapper = ({ + applicationId, + data, + options, +}: { + applicationId: number; + data: ApiBaseCacheSettingPayload; + options?: AzionClientOptions; +}): Promise> => + createCacheSettingMethod(resolveToken(), applicationId, data, options); + +/** + * Function to retrieve a specific cache setting from an application. + * + * @param {Object} params - Parameters for retrieving a cache setting. + * @param {number} params.applicationId - Application ID. + * @param {number} params.cacheSettingId - Cache setting ID. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The retrieved cache setting or an error. + * + * @example + * const { error, data } = await getCacheSetting({ + * applicationId: 1234, + * cacheSettingId: 5678, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to get cache setting:', error); + * } else { + * console.log('Retrieved cache setting:', data.name); + * } + */ +export const getCacheSettingWrapper = ({ + applicationId, + cacheSettingId, + options, +}: { + applicationId: number; + cacheSettingId: number; + options?: AzionClientOptions; +}): Promise> => + getCacheSettingMethod(resolveToken(), applicationId, cacheSettingId, options); + +/** + * Wrapper function to retrieve a list of cache settings for a specific application. + * + * @param {Object} params - Parameters for listing cache settings. + * @param {number} params.applicationId - Application ID. + * @param {ApiListCacheSettingsParams} [params.params] - Parameters for filtering and pagination. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} A collection of cache settings or an error. + * + * @example + * const { error, data } = await getCacheSettings({ + * applicationId: 1234, + * params: { page: 1, page_size: 20 }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to get cache settings:', error); + * } else { + * console.log(`Retrieved ${data.results.length} cache settings`); + * } + */ +export const getCacheSettingsWrapper = ({ + applicationId, + params, + options, +}: { + applicationId: number; + params?: ApiListCacheSettingsParams; + options?: AzionClientOptions; +}): Promise> => + getCacheSettingsMethod(resolveToken(), applicationId, params, options); + +/** + * Wrapper function to update an existing cache setting for a specific application. + * + * @param {Object} params - Parameters for updating a cache setting. + * @param {number} params.applicationId - Application ID. + * @param {number} params.cacheSettingId - Cache setting ID to update. + * @param {ApiUpdateCacheSettingPayload} params.data - Updated data for the cache setting. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The updated cache setting or an error. + * + * @example + * const { error, data } = await updateCacheSetting({ + * applicationId: 1234, + * cacheSettingId: 5678, + * data: { name: 'Updated Cache Setting' }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to update cache setting:', error); + * } else { + * console.log('Updated cache setting:', data.name); + * } + */ +export const updateCacheSettingWrapper = ({ + applicationId, + cacheSettingId, + data, + options, +}: { + applicationId: number; + cacheSettingId: number; + data: ApiUpdateCacheSettingPayload; + options?: AzionClientOptions; +}): Promise> => + updateCacheSettingMethod(resolveToken(), applicationId, cacheSettingId, data, options); + +/** + * Wrapper function to delete a specific cache setting from an application. + * + * @param {Object} params - Parameters for deleting a cache setting. + * @param {number} params.applicationId - Application ID. + * @param {number} params.cacheSettingId - Cache setting ID to delete. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} A response indicating success or an error. + * + * @example + * const { error, data } = await deleteCacheSetting({ + * applicationId: 1234, + * cacheSettingId: 5678, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to delete cache setting:', error); + * } else { + * console.log('Cache setting deleted successfully'); + * } + */ +export const deleteCacheSettingWrapper = ({ + applicationId, + cacheSettingId, + options, +}: { + applicationId: number; + cacheSettingId: number; + options?: AzionClientOptions; +}): Promise> => + deleteCacheSettingMethod(resolveToken(), applicationId, cacheSettingId, options); diff --git a/packages/applications/src/cache-settings/services/index.ts b/packages/applications/src/cache-settings/services/index.ts new file mode 100644 index 0000000..2ef6375 --- /dev/null +++ b/packages/applications/src/cache-settings/services/index.ts @@ -0,0 +1,130 @@ +import { + ApiCreateCacheSettingPayload, + ApiCreateCacheSettingResponse, + ApiDeleteCacheSettingResponse, + ApiGetCacheSettingResponse, + ApiListCacheSettingsParams, + ApiListCacheSettingsResponse, + ApiUpdateCacheSettingPayload, + ApiUpdateCacheSettingResponse, +} from './types'; + +const BASE_URL = 'https://api.azionapi.net/edge_applications'; + +export const getCacheSettings = async ( + token: string, + Id: number, + params?: ApiListCacheSettingsParams, + debug?: boolean, +): Promise => { + try { + const { page = 1, page_size = 10, sort = 'name', order = 'asc' } = params || {}; + const queryParams = new URLSearchParams({ + page: String(page), + page_size: String(page_size), + sort, + order, + }); + const response = await fetch(`${BASE_URL}/${Id}/cache_settings?${queryParams.toString()}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error getting cache settings:', error); + throw error; + } +}; + +export const getCacheSetting = async ( + token: string, + Id: number, + cacheSettingId: number, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/cache_settings/${cacheSettingId}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error getting cache setting by ID:', error); + throw error; + } +}; + +export const postCacheSetting = async ( + token: string, + Id: number, + cacheSettingData: ApiCreateCacheSettingPayload, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/cache_settings`, { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(cacheSettingData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error creating cache setting:', error); + throw error; + } +}; + +export const patchCacheSetting = async ( + token: string, + Id: number, + cacheSettingId: number, + cacheSettingData: ApiUpdateCacheSettingPayload, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/cache_settings/${cacheSettingId}`, { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(cacheSettingData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error updating cache setting:', error); + throw error; + } +}; + +export const deleteCacheSetting = async ( + token: string, + Id: number, + cacheSettingId: number, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/cache_settings/${cacheSettingId}`, { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error deleting cache setting:', error); + throw error; + } +}; diff --git a/packages/applications/src/cache-settings/services/types.ts b/packages/applications/src/cache-settings/services/types.ts new file mode 100644 index 0000000..ac33165 --- /dev/null +++ b/packages/applications/src/cache-settings/services/types.ts @@ -0,0 +1,98 @@ +export enum BrowserCacheSettings { + HONOR = 'honor', + OVERRIDE = 'override', + IGNORE = 'ignore', +} + +export enum CdnCacheSettings { + HONOR = 'honor', + OVERRIDE = 'override', +} + +export enum CacheByQueryString { + IGNORE = 'ignore', + WHITELIST = 'whitelist', + BLACKLIST = 'blacklist', + ALL = 'all', +} + +export enum CacheByCookies { + IGNORE = 'ignore', + WHITELIST = 'whitelist', + BLACKLIST = 'blacklist', + ALL = 'all', +} + +export enum AdaptiveDeliveryAction { + IGNORE = 'ignore', + OPTIMIZE = 'optimize', +} + +export interface ApiBaseCacheSettingPayload { + name: string; + browser_cache_settings?: BrowserCacheSettings; + browser_cache_settings_maximum_ttl?: number; + cdn_cache_settings?: CdnCacheSettings; + cdn_cache_settings_maximum_ttl?: number; + cache_by_query_string?: CacheByQueryString; + query_string_fields?: string[]; + enable_query_string_sort?: boolean; + cache_by_cookies?: CacheByCookies; + cookie_names?: string[]; + adaptive_delivery_action?: AdaptiveDeliveryAction; + device_group?: string[]; + enable_caching_for_post?: boolean; + l2_caching_enabled?: boolean; + is_slice_configuration_enabled?: boolean; + is_slice_edge_caching_enabled?: boolean; + is_slice_l2_caching_enabled?: boolean; + slice_configuration_range?: number; + enable_caching_for_options?: boolean; + enable_stale_cache?: boolean; + l2_region?: string | null; +} + +export interface ApiCreateCacheSettingPayload extends ApiBaseCacheSettingPayload {} + +export interface ApiCacheSetting extends ApiBaseCacheSettingPayload { + id: number; +} + +export interface ApiListCacheSettingsParams { + page?: number; + page_size?: number; + sort?: 'name' | 'id'; + order?: 'asc' | 'desc'; +} + +export interface ApiListCacheSettingsResponse { + count: number; + total_pages: number; + schema_version: number; + links: { + previous: string | null; + next: string | null; + }; + results: ApiCacheSetting[]; +} + +export interface ApiGetCacheSettingResponse { + results: ApiCacheSetting; + schema_version: number; +} + +export interface ApiCreateCacheSettingResponse { + results: ApiCacheSetting; + schema_version: number; +} + +export interface ApiUpdateCacheSettingPayload extends Partial {} + +export interface ApiUpdateCacheSettingResponse { + results: ApiCacheSetting; + schema_version: number; +} + +export interface ApiDeleteCacheSettingResponse { + schema_version: number; +} diff --git a/packages/applications/src/cache-settings/types.ts b/packages/applications/src/cache-settings/types.ts new file mode 100644 index 0000000..a45920c --- /dev/null +++ b/packages/applications/src/cache-settings/types.ts @@ -0,0 +1,10 @@ +import { + ApiBaseCacheSettingPayload, + ApiCacheSetting, + ApiListCacheSettingsParams, + ApiUpdateCacheSettingPayload, +} from './services/types'; + +export type AzionCacheSetting = ApiCacheSetting; + +export { ApiBaseCacheSettingPayload, ApiListCacheSettingsParams, ApiUpdateCacheSettingPayload }; diff --git a/packages/applications/src/device-groups/index.ts b/packages/applications/src/device-groups/index.ts new file mode 100644 index 0000000..f2280ce --- /dev/null +++ b/packages/applications/src/device-groups/index.ts @@ -0,0 +1,323 @@ +import { AzionApplicationCollectionResponse, AzionApplicationResponse, AzionClientOptions } from '../types'; +import { resolveDebug, resolveToken } from '../utils'; +import { + createDeviceGroup, + deleteDeviceGroup, + getDeviceGroupById, + getDeviceGroups, + updateDeviceGroup, +} from './services/index'; +import { ApiCreateDeviceGroupPayload, ApiListDeviceGroupsParams, ApiUpdateDeviceGroupPayload } from './services/types'; +import { AzionDeviceGroup } from './types'; + +/** + * Creates a new device group for a specific application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {ApiCreateDeviceGroupPayload} deviceGroupData - Data for the device group to be created. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} The created device group or an error. + */ +export const createDeviceGroupMethod = async ( + token: string, + Id: number, + deviceGroupData: ApiCreateDeviceGroupPayload, + options?: AzionClientOptions, +): Promise> => { + try { + const { results } = await createDeviceGroup(resolveToken(token), Id, deviceGroupData, resolveDebug(options?.debug)); + return { data: results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to create device group', + operation: 'create device group', + }, + }; + } +}; + +/** + * Deletes a specific device group from an application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {number} deviceGroupId - Device group ID to delete. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} A response indicating success or an error. + */ +export const deleteDeviceGroupMethod = async ( + token: string, + Id: number, + deviceGroupId: number, + options?: AzionClientOptions, +): Promise> => { + try { + await deleteDeviceGroup(resolveToken(token), Id, deviceGroupId, resolveDebug(options?.debug)); + return { data: undefined }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to delete device group', + operation: 'delete device group', + }, + }; + } +}; + +/** + * Retrieves a specific device group from an application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {number} deviceGroupId - Device group ID. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} The retrieved device group or an error. + */ +export const getDeviceGroupMethod = async ( + token: string, + Id: number, + deviceGroupId: number, + options?: AzionClientOptions, +): Promise> => { + try { + const { results } = await getDeviceGroupById(resolveToken(token), Id, deviceGroupId, resolveDebug(options?.debug)); + return { data: results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to get device group', + operation: 'get device group', + }, + }; + } +}; + +/** + * Retrieves a list of device groups for a specific application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {ApiListDeviceGroupsParams} [params] - Parameters for listing device groups. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} A collection of device groups or an error. + */ +export const getDeviceGroupsMethod = async ( + token: string, + Id: number, + params?: ApiListDeviceGroupsParams, + options?: AzionClientOptions, +): Promise> => { + try { + const data = await getDeviceGroups(resolveToken(token), Id, params, resolveDebug(options?.debug)); + return { data }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to get device groups', + operation: 'get device groups', + }, + }; + } +}; + +/** + * Updates an existing device group for a specific application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {number} deviceGroupId - Device group ID to update. + * @param {ApiUpdateDeviceGroupPayload} deviceGroupData - Updated data for the device group. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} The updated device group or an error. + */ +export const updateDeviceGroupMethod = async ( + token: string, + Id: number, + deviceGroupId: number, + deviceGroupData: ApiUpdateDeviceGroupPayload, + options?: AzionClientOptions, +): Promise> => { + try { + const { results } = await updateDeviceGroup( + resolveToken(token), + Id, + deviceGroupId, + deviceGroupData, + resolveDebug(options?.debug), + ); + return { data: results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to update device group', + operation: 'update device group', + }, + }; + } +}; + +/** + * Function to create a new device group for a specific application. + * + * @param {Object} params - Parameters for creating a device group. + * @param {number} params.applicationId - Application ID. + * @param {ApiCreateDeviceGroupPayload} params.data - Data for the device group to be created. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The created device group or an error. + * + * @example + * const { error, data } = await createDeviceGroup({ + * applicationId: 1234, + * data: { name: 'My Device Group', user_agent: 'Mozilla/5.0' }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to create device group:', error); + * } else { + * console.log('Device group created:', data.name); + * } + */ +export const createDeviceGroupWrapper = ({ + applicationId, + data, + options, +}: { + applicationId: number; + data: ApiCreateDeviceGroupPayload; + options?: AzionClientOptions; +}): Promise> => + createDeviceGroupMethod(resolveToken(), applicationId, data, options); + +/** + * Function to delete a specific device group from an application. + * + * @param {Object} params - Parameters for deleting a device group. + * @param {number} params.applicationId - Application ID. + * @param {number} params.deviceGroupId - Device group ID to delete. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} A response indicating success or an error. + * + * @example + * const { error, data } = await deleteDeviceGroup({ + * applicationId: 1234, + * deviceGroupId: 5678, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to delete device group:', error); + * } else { + * console.log('Device group deleted successfully'); + * } + */ +export const deleteDeviceGroupWrapper = ({ + applicationId, + deviceGroupId, + options, +}: { + applicationId: number; + deviceGroupId: number; + options?: AzionClientOptions; +}): Promise> => + deleteDeviceGroupMethod(resolveToken(), applicationId, deviceGroupId, options); + +/** + * Function to retrieve a specific device group from an application. + * + * @param {Object} params - Parameters for retrieving a device group. + * @param {number} params.applicationId - Application ID. + * @param {number} params.deviceGroupId - Device group ID. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The retrieved device group or an error. + * + * @example + * const { error, data } = await getDeviceGroup({ + * applicationId: 1234, + * deviceGroupId: 5678, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to get device group:', error); + * } else { + * console.log('Retrieved device group:', data.name); + * } + */ +export const getDeviceGroupWrapper = ({ + applicationId, + deviceGroupId, + options, +}: { + applicationId: number; + deviceGroupId: number; + options?: AzionClientOptions; +}): Promise> => + getDeviceGroupMethod(resolveToken(), applicationId, deviceGroupId, options); + +/** + * Function to retrieve a list of device groups for a specific application. + * + * @param {Object} params - Parameters for listing device groups. + * @param {number} params.applicationId - Application ID. + * @param {ApiListDeviceGroupsParams} [params.params] - Parameters for filtering and pagination. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} A collection of device groups or an error. + * + * @example + * const { error, data } = await getDeviceGroups({ + * applicationId: 1234, + * params: { page: 1, page_size: 20 }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to get device groups:', error); + * } else { + * console.log(`Retrieved ${data.results.length} device groups`); + * } + */ +export const getDeviceGroupsWrapper = ({ + applicationId, + params, + options, +}: { + applicationId: number; + params?: ApiListDeviceGroupsParams; + options?: AzionClientOptions; +}): Promise> => + getDeviceGroupsMethod(resolveToken(), applicationId, params, options); + +/** + * Function to update an existing device group for a specific application. + * + * @param {Object} params - Parameters for updating a device group. + * @param {number} params.applicationId - Application ID. + * @param {number} params.deviceGroupId - Device group ID to update. + * @param {ApiUpdateDeviceGroupPayload} params.data - Updated data for the device group. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The updated device group or an error. + * + * @example + * const { error, data } = await updateDeviceGroup({ + * applicationId: 1234, + * deviceGroupId: 5678, + * data: { name: 'Updated Device Group' }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to update device group:', error); + * } else { + * console.log('Updated device group:', data.name); + * } + */ +export const updateDeviceGroupWrapper = ({ + applicationId, + deviceGroupId, + data, + options, +}: { + applicationId: number; + deviceGroupId: number; + data: ApiUpdateDeviceGroupPayload; + options?: AzionClientOptions; +}): Promise> => + updateDeviceGroupMethod(resolveToken(), applicationId, deviceGroupId, data, options); diff --git a/packages/applications/src/device-groups/services/index.ts b/packages/applications/src/device-groups/services/index.ts new file mode 100644 index 0000000..a9e6858 --- /dev/null +++ b/packages/applications/src/device-groups/services/index.ts @@ -0,0 +1,178 @@ +import { + ApiCreateDeviceGroupPayload, + ApiCreateDeviceGroupResponse, + ApiDeleteDeviceGroupResponse, + ApiGetDeviceGroupResponse, + ApiListDeviceGroupsParams, + ApiListDeviceGroupsResponse, + ApiUpdateDeviceGroupPayload, + ApiUpdateDeviceGroupResponse, +} from './types'; + +const BASE_URL = 'https://api.azionapi.net/edge_applications'; + +/** + * Lists all device groups for an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {ApiListDeviceGroupsParams} [params] - Optional parameters for filtering and pagination. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Array of device groups or an error if retrieval failed. + */ +const getDeviceGroups = async ( + token: string, + Id: number, + params?: ApiListDeviceGroupsParams, + debug?: boolean, +): Promise => { + try { + const { page = 1, page_size = 10, sort = 'name', order = 'asc' } = params || {}; + const queryParams = new URLSearchParams({ + page: String(page), + page_size: String(page_size), + sort, + order, + }); + const response = await fetch(`${BASE_URL}/${Id}/device_groups?${queryParams.toString()}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error getting device groups:', error); + throw error; + } +}; + +/** + * Retrieves a specific device group by ID. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {number} deviceGroupId - ID of the device group. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Device group data or an error if retrieval failed. + */ +const getDeviceGroupById = async ( + token: string, + Id: number, + deviceGroupId: number, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/device_groups/${deviceGroupId}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error getting device group by ID:', error); + throw error; + } +}; + +/** + * Creates a new device group for an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {ApiCreateDeviceGroupPayload} deviceGroupData - Data of the device group to be created. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The created device group or an error if creation failed. + */ +const createDeviceGroup = async ( + token: string, + Id: number, + deviceGroupData: ApiCreateDeviceGroupPayload, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/device_groups`, { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(deviceGroupData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error creating device group:', error); + throw error; + } +}; + +/** + * Updates an existing device group. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {number} deviceGroupId - ID of the device group to update. + * @param {ApiUpdateDeviceGroupPayload} deviceGroupData - New data for the device group. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The updated device group or an error if update failed. + */ +const updateDeviceGroup = async ( + token: string, + Id: number, + deviceGroupId: number, + deviceGroupData: ApiUpdateDeviceGroupPayload, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/device_groups/${deviceGroupId}`, { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(deviceGroupData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error updating device group:', error); + throw error; + } +}; + +/** + * Deletes a device group. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {number} deviceGroupId - ID of the device group to delete. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Confirmation of deletion or an error if deletion failed. + */ +const deleteDeviceGroup = async ( + token: string, + Id: number, + deviceGroupId: number, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/device_groups/${deviceGroupId}`, { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error deleting device group:', error); + throw error; + } +}; + +export { createDeviceGroup, deleteDeviceGroup, getDeviceGroupById, getDeviceGroups, updateDeviceGroup }; diff --git a/packages/applications/src/device-groups/services/types.ts b/packages/applications/src/device-groups/services/types.ts new file mode 100644 index 0000000..cc03a30 --- /dev/null +++ b/packages/applications/src/device-groups/services/types.ts @@ -0,0 +1,49 @@ +export interface ApiBaseDeviceGroupPayload { + name: string; + user_agent: string; +} + +export interface ApiCreateDeviceGroupPayload extends ApiBaseDeviceGroupPayload {} + +export interface ApiDeviceGroup extends ApiBaseDeviceGroupPayload { + id: number; +} + +export interface ApiListDeviceGroupsParams { + page?: number; + page_size?: number; + sort?: 'name' | 'id'; + order?: 'asc' | 'desc'; +} + +export interface ApiListDeviceGroupsResponse { + count: number; + total_pages: number; + schema_version: number; + links: { + previous: string | null; + next: string | null; + }; + results: ApiDeviceGroup[]; +} + +export interface ApiGetDeviceGroupResponse { + results: ApiDeviceGroup; + schema_version: number; +} + +export interface ApiCreateDeviceGroupResponse { + results: ApiDeviceGroup; + schema_version: number; +} + +export interface ApiUpdateDeviceGroupPayload extends Partial {} + +export interface ApiUpdateDeviceGroupResponse { + results: ApiDeviceGroup; + schema_version: number; +} + +export interface ApiDeleteDeviceGroupResponse { + schema_version: number; +} diff --git a/packages/applications/src/device-groups/types.ts b/packages/applications/src/device-groups/types.ts new file mode 100644 index 0000000..8fe4474 --- /dev/null +++ b/packages/applications/src/device-groups/types.ts @@ -0,0 +1,5 @@ +export interface AzionDeviceGroup { + id: number; + name: string; + user_agent: string; +} diff --git a/packages/applications/src/functions-instances/index.ts b/packages/applications/src/functions-instances/index.ts new file mode 100644 index 0000000..de5a8a4 --- /dev/null +++ b/packages/applications/src/functions-instances/index.ts @@ -0,0 +1,344 @@ +import { AzionApplicationCollectionResponse, AzionApplicationResponse, AzionClientOptions } from '../types'; +import { resolveDebug, resolveToken } from '../utils'; +import { + createFunctionInstance, + deleteFunctionInstance, + getFunctionInstanceById, + listFunctionInstances, + updateFunctionInstance, +} from './services/index'; +import { + ApiCreateFunctionInstancePayload, + ApiListFunctionInstancesParams, + ApiUpdateFunctionInstancePayload, +} from './services/types'; +import { AzionFunctionInstance } from './types'; + +/** + * Creates a new function instance for a specific application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {ApiCreateFunctionInstancePayload} functionInstanceData - Data for the function instance to be created. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} The created function instance or an error. + */ +export const createFunctionInstanceMethod = async ( + token: string, + Id: number, + functionInstanceData: ApiCreateFunctionInstancePayload, + options?: AzionClientOptions, +): Promise> => { + try { + const apiResponse = await createFunctionInstance( + resolveToken(token), + Id, + functionInstanceData, + resolveDebug(options?.debug), + ); + return { data: apiResponse.results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to create function instance', + operation: 'create function instance', + }, + }; + } +}; + +/** + * Deletes a function instance from a specific application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {number} functionInstanceId - ID of the function instance to delete. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} Confirmation of deletion or an error. + */ +export const deleteFunctionInstanceMethod = async ( + token: string, + Id: number, + functionInstanceId: number, + options?: AzionClientOptions, +): Promise> => { + try { + await deleteFunctionInstance(resolveToken(token), Id, functionInstanceId, resolveDebug(options?.debug)); + return { data: undefined }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to delete function instance', + operation: 'delete function instance', + }, + }; + } +}; + +/** + * Retrieves a specific function instance from an application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {number} functionInstanceId - ID of the function instance to retrieve. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} The retrieved function instance or an error. + */ +export const getFunctionInstanceMethod = async ( + token: string, + Id: number, + functionInstanceId: number, + options?: AzionClientOptions, +): Promise> => { + try { + const { results } = await getFunctionInstanceById( + resolveToken(token), + Id, + functionInstanceId, + resolveDebug(options?.debug), + ); + return { data: results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to get function instance', + operation: 'get function instance', + }, + }; + } +}; + +/** + * Lists all function instances for a specific application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {ApiListFunctionInstancesParams} [params] - Optional parameters for filtering and pagination. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} A collection of function instances or an error. + */ +export const getFunctionInstancesMethod = async ( + token: string, + Id: number, + params?: ApiListFunctionInstancesParams, + options?: AzionClientOptions, +): Promise> => { + try { + const data = await listFunctionInstances(resolveToken(token), Id, params, resolveDebug(options?.debug)); + return { data }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to get function instances', + operation: 'get function instances', + }, + }; + } +}; + +/** + * Updates an existing function instance in a specific application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - Application ID. + * @param {number} functionInstanceId - ID of the function instance to update. + * @param {ApiUpdateFunctionInstancePayload} functionInstanceData - New data for the function instance. + * @param {AzionClientOptions} [options] - Client options including debug mode. + * @returns {Promise>} The updated function instance or an error. + */ +export const updateFunctionInstanceMethod = async ( + token: string, + Id: number, + functionInstanceId: number, + functionInstanceData: ApiUpdateFunctionInstancePayload, + options?: AzionClientOptions, +): Promise> => { + try { + const { results } = await updateFunctionInstance( + resolveToken(token), + Id, + functionInstanceId, + functionInstanceData, + resolveDebug(options?.debug), + ); + return { data: results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to update function instance', + operation: 'update function instance', + }, + }; + } +}; + +/** + * Create a new function instance for a specific application. + * + * @param {Object} params - Parameters for creating a function instance. + * @param {number} params.applicationId - Application ID. + * @param {ApiCreateFunctionInstancePayload} params.data - Data for the function instance to be created. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The created function instance or an error. + * + * @example + * const { error, data } = await createFunctionInstance({ + * applicationId: 1234, + * data: { + * name: 'My Function Instance', + * edge_function_id: 5678, + * args: {} + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to create function instance:', error); + * } else { + * console.log('Function instance created:', data.name); + * } + */ +export const createFunctionInstanceWrapper = ({ + applicationId, + data, + options, +}: { + applicationId: number; + data: ApiCreateFunctionInstancePayload; + options?: AzionClientOptions; +}): Promise> => + createFunctionInstanceMethod(resolveToken(), applicationId, data, options); + +/** + * Delete a function instance from a specific application. + * + * @param {Object} params - Parameters for deleting a function instance. + * @param {number} params.applicationId - Application ID. + * @param {number} params.functionInstanceId - ID of the function instance to delete. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} Confirmation of deletion or an error. + * + * @example + * const { error, data } = await deleteFunctionInstance({ + * applicationId: 1234, + * functionInstanceId: 5678, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to delete function instance:', error); + * } else { + * console.log('Function instance deleted successfully'); + * } + */ +export const deleteFunctionInstanceWrapper = ({ + applicationId, + functionInstanceId, + options, +}: { + applicationId: number; + functionInstanceId: number; + options?: AzionClientOptions; +}): Promise> => + deleteFunctionInstanceMethod(resolveToken(), applicationId, functionInstanceId, options); + +/** + * Retrieve a specific function instance from an application. + * + * @param {Object} params - Parameters for retrieving a function instance. + * @param {number} params.applicationId - Application ID. + * @param {number} params.functionInstanceId - ID of the function instance to retrieve. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The retrieved function instance or an error. + * + * @example + * const { error, data } = await getFunctionInstance({ + * applicationId: 1234, + * functionInstanceId: 5678, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to get function instance:', error); + * } else { + * console.log('Retrieved function instance:', data.name); + * } + */ +export const getFunctionInstanceWrapper = ({ + applicationId, + functionInstanceId, + options, +}: { + applicationId: number; + functionInstanceId: number; + options?: AzionClientOptions; +}): Promise> => + getFunctionInstanceMethod(resolveToken(), applicationId, functionInstanceId, options); + +/** + * List all function instances for a specific application. + * + * @param {Object} params - Parameters for listing function instances. + * @param {number} params.applicationId - Application ID. + * @param {ApiListFunctionInstancesParams} [params.params] - Optional parameters for filtering and pagination. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} A collection of function instances or an error. + * + * @example + * const { error, data } = await getFunctionInstances({ + * applicationId: 1234, + * params: { page: 1, page_size: 20, sort: 'name', order: 'asc' }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to get function instances:', error); + * } else { + * console.log('Function instances:', data.results); + * } + */ +export const getFunctionInstancesWrapper = ({ + applicationId, + params, + options, +}: { + applicationId: number; + params?: ApiListFunctionInstancesParams; + options?: AzionClientOptions; +}): Promise> => + getFunctionInstancesMethod(resolveToken(), applicationId, params, options); + +/** + * Update an existing function instance in a specific application. + * + * @param {Object} params - Parameters for updating a function instance. + * @param {number} params.applicationId - Application ID. + * @param {number} params.functionInstanceId - ID of the function instance to update. + * @param {ApiUpdateFunctionInstancePayload} params.data - New data for the function instance. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The updated function instance or an error. + * + * @example + * const { error, data } = await updateFunctionInstance({ + * applicationId: 1234, + * functionInstanceId: 5678, + * data: { + * name: 'Updated Function Instance', + * args: { key: 'new value' } + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to update function instance:', error); + * } else { + * console.log('Updated function instance:', data); + * } + */ +export const updateFunctionInstanceWrapper = ({ + applicationId, + functionInstanceId, + data, + options, +}: { + applicationId: number; + functionInstanceId: number; + data: ApiUpdateFunctionInstancePayload; + options?: AzionClientOptions; +}): Promise> => + updateFunctionInstanceMethod(resolveToken(), applicationId, functionInstanceId, data, options); diff --git a/packages/applications/src/functions-instances/services/index.ts b/packages/applications/src/functions-instances/services/index.ts new file mode 100644 index 0000000..c530084 --- /dev/null +++ b/packages/applications/src/functions-instances/services/index.ts @@ -0,0 +1,185 @@ +import { + ApiCreateFunctionInstancePayload, + ApiCreateFunctionInstanceResponse, + ApiDeleteFunctionInstanceResponse, + ApiGetFunctionInstanceResponse, + ApiListFunctionInstancesParams, + ApiListFunctionInstancesResponse, + ApiUpdateFunctionInstancePayload, + ApiUpdateFunctionInstanceResponse, +} from './types'; + +const BASE_URL = 'https://api.azionapi.net/edge_applications'; + +/** + * Lists all function instances for an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {ApiListFunctionInstancesParams} [params] - Optional parameters for filtering and pagination. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Array of function instances or an error if retrieval failed. + */ +const listFunctionInstances = async ( + token: string, + Id: number, + params?: ApiListFunctionInstancesParams, + debug?: boolean, +): Promise => { + try { + const { page = 1, page_size = 10, sort = 'asc', order_by = 'name', filter = '' } = params || {}; + const queryParams = new URLSearchParams({ + page: String(page), + page_size: String(page_size), + sort, + order_by, + filter, + }); + const response = await fetch(`${BASE_URL}/${Id}/functions_instances?${queryParams.toString()}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error listing function instances:', error); + throw error; + } +}; + +/** + * Retrieves a specific function instance by ID. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {number} functionInstanceId - ID of the function instance. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Function instance data or an error if retrieval failed. + */ +const getFunctionInstanceById = async ( + token: string, + Id: number, + functionInstanceId: number, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/functions_instances/${functionInstanceId}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error getting function instance by ID:', error); + throw error; + } +}; + +/** + * Creates a new function instance for an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} edgeApplicationId - ID of the edge application. + * @param {ApiCreateFunctionInstancePayload} functionInstanceData - Data of the function instance to be created. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The created function instance or an error if creation failed. + */ +const createFunctionInstance = async ( + token: string, + edgeApplicationId: number, + functionInstanceData: ApiCreateFunctionInstancePayload, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${edgeApplicationId}/functions_instances`, { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(functionInstanceData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error creating function instance:', error); + throw error; + } +}; + +/** + * Updates an existing function instance. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {number} functionInstanceId - ID of the function instance to update. + * @param {ApiUpdateFunctionInstancePayload} functionInstanceData - New data for the function instance. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The updated function instance or an error if update failed. + */ +const updateFunctionInstance = async ( + token: string, + Id: number, + functionInstanceId: number, + functionInstanceData: ApiUpdateFunctionInstancePayload, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/functions_instances/${functionInstanceId}`, { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(functionInstanceData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error updating function instance:', error); + throw error; + } +}; + +/** + * Deletes a function instance. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} edgeApplicationId - ID of the edge application. + * @param {number} edgeFunctionInstanceId - ID of the function instance to delete. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Confirmation of deletion or an error if deletion failed. + */ +const deleteFunctionInstance = async ( + token: string, + edgeApplicationId: number, + edgeFunctionInstanceId: number, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${edgeApplicationId}/functions_instances/${edgeFunctionInstanceId}`, { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error deleting function instance:', error); + throw error; + } +}; + +export { + createFunctionInstance, + deleteFunctionInstance, + getFunctionInstanceById, + listFunctionInstances, + updateFunctionInstance, +}; diff --git a/packages/applications/src/functions-instances/services/types.ts b/packages/applications/src/functions-instances/services/types.ts new file mode 100644 index 0000000..fc8d7ad --- /dev/null +++ b/packages/applications/src/functions-instances/services/types.ts @@ -0,0 +1,59 @@ +export interface ApiBaseFunctionInstancePayload { + name: string; + code: string; + language: 'JavaScript'; + initiator_type: 'edge_application' | 'edge_firewall'; + active: boolean; + json_args: Record; +} + +export interface ApiCreateFunctionInstancePayload { + name: string; + edge_function_id: number; + args: Record; +} + +export interface ApiFunctionInstance extends ApiBaseFunctionInstancePayload { + id: number; +} + +export interface ApiListFunctionInstancesParams { + page?: number; + page_size?: number; + sort?: 'name' | 'id'; + order?: 'asc' | 'desc'; + order_by?: string; + filter?: string; +} + +export interface ApiListFunctionInstancesResponse { + count: number; + total_pages: number; + schema_version: number; + links: { + previous: string | null; + next: string | null; + }; + results: ApiFunctionInstance[]; +} + +export interface ApiGetFunctionInstanceResponse { + results: ApiFunctionInstance; + schema_version: number; +} + +export interface ApiCreateFunctionInstanceResponse { + results: ApiFunctionInstance; + schema_version: number; +} + +export interface ApiUpdateFunctionInstancePayload extends Partial {} + +export interface ApiUpdateFunctionInstanceResponse { + results: ApiFunctionInstance; + schema_version: number; +} + +export interface ApiDeleteFunctionInstanceResponse { + schema_version: number; +} diff --git a/packages/applications/src/functions-instances/types.ts b/packages/applications/src/functions-instances/types.ts new file mode 100644 index 0000000..cb3a853 --- /dev/null +++ b/packages/applications/src/functions-instances/types.ts @@ -0,0 +1,10 @@ +import { + ApiBaseFunctionInstancePayload, + ApiFunctionInstance, + ApiListFunctionInstancesParams, + ApiUpdateFunctionInstancePayload, +} from './services/types'; + +export type AzionFunctionInstance = ApiFunctionInstance; + +export { ApiBaseFunctionInstancePayload, ApiListFunctionInstancesParams, ApiUpdateFunctionInstancePayload }; diff --git a/packages/applications/src/index.ts b/packages/applications/src/index.ts new file mode 100644 index 0000000..52d7141 --- /dev/null +++ b/packages/applications/src/index.ts @@ -0,0 +1,178 @@ +import { + createApplicationMethod, + deleteApplicationMethod, + getApplicationMethod, + getApplicationsMethod, + patchApplicationMethod, + putApplicationMethod, +} from './main-settings/index'; + +import { + createApplicationWrapper, + deleteApplicationWrapper, + getApplicationWrapper, + getApplicationsWrapper, + patchApplicationWrapper, + putApplicationWrapper, +} from './main-settings/index'; + +import { + createCacheSettingWrapper, + deleteCacheSettingWrapper, + getCacheSettingWrapper, + getCacheSettingsWrapper, + updateCacheSettingWrapper, +} from './cache-settings/index'; + +import { + createDeviceGroupWrapper, + deleteDeviceGroupWrapper, + getDeviceGroupWrapper, + getDeviceGroupsWrapper, + updateDeviceGroupWrapper, +} from './device-groups/index'; + +import { + createFunctionInstanceWrapper, + deleteFunctionInstanceWrapper, + getFunctionInstanceWrapper, + getFunctionInstancesWrapper, + updateFunctionInstanceWrapper, +} from './functions-instances/index'; + +import { + createOriginWrapper, + deleteOriginWrapper, + getOriginWrapper, + getOriginsWrapper, + updateOriginWrapper, +} from './origins/index'; + +import { + createRuleWrapper, + deleteRuleWrapper, + getRuleWrapper, + getRulesWrapper, + updateRuleWrapper, +} from './rules-engine/index'; + +import type { + AzionApplicationClient, + AzionApplicationCollectionOptions, + AzionClientOptions, + CreateAzionApplicationClient, +} from './types'; + +import { ApiCreateApplicationPayload, ApiUpdateApplicationPayload } from './main-settings/services/types'; + +import { resolveDebug, resolveToken } from './utils'; + +const createAzionApplicationClient: CreateAzionApplicationClient = ( + config?: Partial<{ token: string; options?: AzionClientOptions }>, +): AzionApplicationClient => { + const tokenValue = resolveToken(config?.token); + const debugValue = resolveDebug(config?.options?.debug); + + const client: AzionApplicationClient = { + createApplication: async ({ data }: { data: ApiCreateApplicationPayload; options?: AzionClientOptions }) => + createApplicationMethod(tokenValue, data, { ...config, debug: debugValue }), + deleteApplication: async ({ applicationId, options }: { applicationId: number; options?: AzionClientOptions }) => + deleteApplicationMethod(tokenValue, applicationId, { + ...config?.options, + ...options, + debug: resolveDebug(config?.options?.debug ?? options?.debug), + }), + getApplication: async ({ applicationId, options }: { applicationId: number; options?: AzionClientOptions }) => + getApplicationMethod(tokenValue, applicationId, { + ...config?.options, + ...options, + debug: resolveDebug(config?.options?.debug ?? options?.debug), + }), + getApplications: async ({ + params, + options, + }: { + params?: AzionApplicationCollectionOptions; + options?: AzionClientOptions; + }) => + getApplicationsMethod(tokenValue, params, { + ...config?.options, + ...options, + debug: resolveDebug(options?.debug), + }), + putApplication: async ({ + applicationId, + data, + options, + }: { + applicationId: number; + data: ApiUpdateApplicationPayload; + options?: AzionClientOptions; + }) => + putApplicationMethod(tokenValue, applicationId, data, { + ...config?.options, + ...options, + debug: resolveDebug(options?.debug), + }), + patchApplication: async ({ + applicationId, + data, + options, + }: { + applicationId: number; + data: Partial; + options?: AzionClientOptions; + }) => + patchApplicationMethod(tokenValue, applicationId, data, { + ...config?.options, + ...options, + debug: resolveDebug(options?.debug), + }), + }; + + return client; +}; + +export * from './cache-settings/types'; +export * from './device-groups/types'; +export * from './functions-instances/types'; +export * from './main-settings/types'; +export * from './origins/types'; +export * from './rules-engine/types'; +export * from './types'; + +export { + createApplicationWrapper as createApplication, + createAzionApplicationClient, + createCacheSettingWrapper as createCacheSetting, + createDeviceGroupWrapper as createDeviceGroup, + createFunctionInstanceWrapper as createFunctionInstance, + createOriginWrapper as createOrigin, + createRuleWrapper as createRule, + deleteApplicationWrapper as deleteApplication, + deleteCacheSettingWrapper as deleteCacheSetting, + deleteDeviceGroupWrapper as deleteDeviceGroup, + deleteFunctionInstanceWrapper as deleteFunctionInstance, + deleteOriginWrapper as deleteOrigin, + deleteRuleWrapper as deleteRule, + getApplicationWrapper as getApplication, + getApplicationsWrapper as getApplications, + getCacheSettingWrapper as getCacheSetting, + getCacheSettingsWrapper as getCacheSettings, + getDeviceGroupWrapper as getDeviceGroup, + getDeviceGroupsWrapper as getDeviceGroups, + getFunctionInstanceWrapper as getFunctionInstance, + getFunctionInstancesWrapper as getFunctionInstances, + getOriginWrapper as getOrigin, + getOriginsWrapper as getOrigins, + getRuleWrapper as getRule, + getRulesWrapper as getRules, + patchApplicationWrapper as patchApplication, + putApplicationWrapper as putApplication, + updateCacheSettingWrapper as updateCacheSetting, + updateDeviceGroupWrapper as updateDeviceGroup, + updateFunctionInstanceWrapper as updateFunctionInstance, + updateOriginWrapper as updateOrigin, + updateRuleWrapper as updateRule, +}; +export default createAzionApplicationClient; diff --git a/packages/applications/src/main-settings/index.ts b/packages/applications/src/main-settings/index.ts new file mode 100644 index 0000000..7df4657 --- /dev/null +++ b/packages/applications/src/main-settings/index.ts @@ -0,0 +1,1056 @@ +import { + createCacheSettingMethod, + deleteCacheSettingMethod, + getCacheSettingMethod, + getCacheSettingsMethod, + updateCacheSettingMethod, +} from '../cache-settings/index'; +import { + createDeviceGroupMethod, + deleteDeviceGroupMethod, + getDeviceGroupMethod, + getDeviceGroupsMethod, + updateDeviceGroupMethod, +} from '../device-groups/index'; +import { + createFunctionInstanceMethod, + deleteFunctionInstanceMethod, + getFunctionInstanceMethod, + getFunctionInstancesMethod, + updateFunctionInstanceMethod, +} from '../functions-instances/index'; +import { + createOriginMethod, + deleteOriginMethod, + getOriginMethod, + getOriginsMethod, + updateOriginMethod, +} from '../origins/index'; +import { + createRuleMethod, + deleteRuleMethod, + getRuleMethod, + getRulesMethod, + updateRuleMethod, +} from '../rules-engine/index'; +import { + AzionApplication, + AzionApplicationCollectionResponse, + AzionApplicationResponse, + AzionClientOptions, +} from '../types'; +import { mapApiError, resolveDebug, resolveToken } from '../utils'; +import { + deleteApplication as deleteApplicationApi, + getApplicationById as getApplicationByIdApi, + getApplications as getApplicationsApi, + patchApplication as patchApplicationApi, + postApplication as postApplicationApi, + putApplication as putApplicationApi, +} from './services/index'; +import { ApiCreateApplicationPayload, ApiListApplicationsParams, ApiUpdateApplicationPayload } from './services/types'; + +/** + * Creates a new Azion Edge Application. + * + * @async + * @function createApplicationMethod + * @param {string} token - The authentication token. + * @param {ApiCreateApplicationPayload} applicationData - The data for the new application. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} A promise that resolves with the created application data or an error. + */ +export const createApplicationMethod = async ( + token: string, + applicationData: ApiCreateApplicationPayload, + options?: AzionClientOptions, +): Promise> => { + const apiResponse = await postApplicationApi(resolveToken(token), applicationData, resolveDebug(options?.debug)); + + if (!apiResponse || !apiResponse.results) { + return { + error: mapApiError(apiResponse, 'post application', 'Failed to post application'), + }; + } + + const appId = apiResponse.results?.id; + + const application: AzionApplication = { + ...apiResponse.results, + cache: { + createCacheSetting: (params) => + createCacheSettingMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getCacheSetting: (params) => + getCacheSettingMethod(token, appId, params.cacheSettingId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getCacheSettings: (params) => + getCacheSettingsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateCacheSetting: (params) => + updateCacheSettingMethod(token, appId, params.cacheSettingId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteCacheSetting: (params) => + deleteCacheSettingMethod(token, appId, params.cacheSettingId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + origins: { + createOrigin: (params) => + createOriginMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getOrigin: (params) => + getOriginMethod(token, appId, params.originKey, { ...options, debug: resolveDebug(options?.debug) }), + getOrigins: (params) => + getOriginsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateOrigin: (params) => + updateOriginMethod(token, appId, params.originKey, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteOrigin: (params) => + deleteOriginMethod(token, appId, params.originKey, { ...options, debug: resolveDebug(options?.debug) }), + }, + rules: { + request: { + createRule: (params) => + createRuleMethod(token, appId, 'request', params.data, { ...options, debug: resolveDebug(options?.debug) }), + getRule: (params) => + getRuleMethod(token, appId, 'request', params.ruleId, { ...options, debug: resolveDebug(options?.debug) }), + getRules: (params) => + getRulesMethod(token, appId, 'request', params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateRule: (params) => + updateRuleMethod(token, appId, 'request', params.ruleId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteRule: (params) => + deleteRuleMethod(token, appId, 'request', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + response: { + createRule: (params) => + createRuleMethod(token, appId, 'response', params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + getRule: (params) => + getRuleMethod(token, appId, 'response', params.ruleId, { ...options, debug: resolveDebug(options?.debug) }), + getRules: (params) => + getRulesMethod(token, appId, 'response', params.params, { + ...options, + debug: resolveDebug(options?.debug), + }), + updateRule: (params) => + updateRuleMethod(token, appId, 'response', params.ruleId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteRule: (params) => + deleteRuleMethod(token, appId, 'response', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + }, + devices: { + createDeviceGroup: (params) => + createDeviceGroupMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getDeviceGroup: (params) => + getDeviceGroupMethod(token, appId, params.deviceGroupId, { ...options, debug: resolveDebug(options?.debug) }), + getDeviceGroups: (params) => + getDeviceGroupsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateDeviceGroup: (params) => + updateDeviceGroupMethod(token, appId, params.deviceGroupId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteDeviceGroup: (params) => + deleteDeviceGroupMethod(token, appId, params.deviceGroupId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + functions: { + createFunctionInstance: (params) => + createFunctionInstanceMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getFunctionInstance: (params) => + getFunctionInstanceMethod(token, appId, params.functionInstanceId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getFunctionInstances: (params) => + getFunctionInstancesMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateFunctionInstance: (params) => + updateFunctionInstanceMethod(token, appId, params.functionInstanceId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteFunctionInstance: (params) => + deleteFunctionInstanceMethod(token, appId, params.functionInstanceId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + }; + + return { data: application }; +}; + +/** + * Retrieves a specific Azion Edge Application by ID. + * + * @async + * @function getApplicationMethod + * @param {string} token - The authentication token. + * @param {number} applicationId - The ID of the application to retrieve. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} A promise that resolves with the application data or an error. + */ +export const getApplicationMethod = async ( + token: string, + applicationId: number, + options?: AzionClientOptions, +): Promise> => { + const apiResponse = await getApplicationByIdApi(resolveToken(token), applicationId, resolveDebug(options?.debug)); + + if (!apiResponse || !apiResponse.results) { + return { + error: mapApiError(apiResponse, 'get application', 'Failed to get application'), + }; + } + + const appId = apiResponse.results.id; + + const application: AzionApplication = { + ...apiResponse.results, + cache: { + createCacheSetting: (params) => + createCacheSettingMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getCacheSetting: (params) => + getCacheSettingMethod(token, appId, params.cacheSettingId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getCacheSettings: (params) => + getCacheSettingsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateCacheSetting: (params) => + updateCacheSettingMethod(token, appId, params.cacheSettingId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteCacheSetting: (params) => + deleteCacheSettingMethod(token, appId, params.cacheSettingId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + origins: { + createOrigin: (params) => + createOriginMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getOrigin: (params) => + getOriginMethod(token, appId, params.originKey, { ...options, debug: resolveDebug(options?.debug) }), + getOrigins: (params) => + getOriginsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateOrigin: (params) => + updateOriginMethod(token, appId, params.originKey, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteOrigin: (params) => + deleteOriginMethod(token, appId, params.originKey, { ...options, debug: resolveDebug(options?.debug) }), + }, + rules: { + request: { + createRule: (params) => + createRuleMethod(token, appId, 'request', params.data, { ...options, debug: resolveDebug(options?.debug) }), + getRule: (params) => + getRuleMethod(token, appId, 'request', params.ruleId, { ...options, debug: resolveDebug(options?.debug) }), + getRules: (params) => + getRulesMethod(token, appId, 'request', params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateRule: (params) => + updateRuleMethod(token, appId, 'request', params.ruleId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteRule: (params) => + deleteRuleMethod(token, appId, 'request', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + response: { + createRule: (params) => + createRuleMethod(token, appId, 'response', params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + getRule: (params) => + getRuleMethod(token, appId, 'response', params.ruleId, { ...options, debug: resolveDebug(options?.debug) }), + getRules: (params) => + getRulesMethod(token, appId, 'response', params.params, { + ...options, + debug: resolveDebug(options?.debug), + }), + updateRule: (params) => + updateRuleMethod(token, appId, 'response', params.ruleId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteRule: (params) => + deleteRuleMethod(token, appId, 'response', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + }, + devices: { + createDeviceGroup: (params) => + createDeviceGroupMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getDeviceGroup: (params) => + getDeviceGroupMethod(token, appId, params.deviceGroupId, { ...options, debug: resolveDebug(options?.debug) }), + getDeviceGroups: (params) => + getDeviceGroupsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateDeviceGroup: (params) => + updateDeviceGroupMethod(token, appId, params.deviceGroupId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteDeviceGroup: (params) => + deleteDeviceGroupMethod(token, appId, params.deviceGroupId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + functions: { + createFunctionInstance: (params) => + createFunctionInstanceMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getFunctionInstance: (params) => + getFunctionInstanceMethod(token, appId, params.functionInstanceId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getFunctionInstances: (params) => + getFunctionInstancesMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateFunctionInstance: (params) => + updateFunctionInstanceMethod(token, appId, params.functionInstanceId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteFunctionInstance: (params) => + deleteFunctionInstanceMethod(token, appId, params.functionInstanceId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + }; + + return { data: application }; +}; + +/** + * Retrieves a list of Azion Edge Applications. + * + * @async + * @function getApplicationsMethod + * @param {string} token - The authentication token. + * @param {ApiListApplicationsParams} [params] - Optional parameters for filtering and pagination. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} A promise that resolves with a collection of applications or an error. + */ +export const getApplicationsMethod = async ( + token: string, + params?: ApiListApplicationsParams, + options?: AzionClientOptions, +): Promise> => { + const apiResponse = await getApplicationsApi(resolveToken(token), params, resolveDebug(options?.debug)); + + if (!apiResponse || !apiResponse.results) { + return { + error: mapApiError(apiResponse, 'get applications', 'Failed to get applications'), + }; + } + + const results: AzionApplication[] = apiResponse.results.map((application) => { + const appId = application.id; + return { + ...application, + cache: { + createCacheSetting: (params) => + createCacheSettingMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getCacheSetting: (params) => + getCacheSettingMethod(token, appId, params.cacheSettingId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getCacheSettings: (params) => + getCacheSettingsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateCacheSetting: (params) => + updateCacheSettingMethod(token, appId, params.cacheSettingId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteCacheSetting: (params) => + deleteCacheSettingMethod(token, appId, params.cacheSettingId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + origins: { + createOrigin: (params) => + createOriginMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getOrigin: (params) => + getOriginMethod(token, appId, params.originKey, { ...options, debug: resolveDebug(options?.debug) }), + getOrigins: (params) => + getOriginsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateOrigin: (params) => + updateOriginMethod(token, appId, params.originKey, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteOrigin: (params) => + deleteOriginMethod(token, appId, params.originKey, { ...options, debug: resolveDebug(options?.debug) }), + }, + rules: { + request: { + createRule: (params) => + createRuleMethod(token, appId, 'request', params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + getRule: (params) => + getRuleMethod(token, appId, 'request', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getRules: (params) => + getRulesMethod(token, appId, 'request', params.params, { + ...options, + debug: resolveDebug(options?.debug), + }), + updateRule: (params) => + updateRuleMethod(token, appId, 'request', params.ruleId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteRule: (params) => + deleteRuleMethod(token, appId, 'request', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + response: { + createRule: (params) => + createRuleMethod(token, appId, 'response', params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + getRule: (params) => + getRuleMethod(token, appId, 'response', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getRules: (params) => + getRulesMethod(token, appId, 'response', params.params, { + ...options, + debug: resolveDebug(options?.debug), + }), + updateRule: (params) => + updateRuleMethod(token, appId, 'response', params.ruleId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteRule: (params) => + deleteRuleMethod(token, appId, 'response', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + }, + devices: { + createDeviceGroup: (params) => + createDeviceGroupMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getDeviceGroup: (params) => + getDeviceGroupMethod(token, appId, params.deviceGroupId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getDeviceGroups: (params) => + getDeviceGroupsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateDeviceGroup: (params) => + updateDeviceGroupMethod(token, appId, params.deviceGroupId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteDeviceGroup: (params) => + deleteDeviceGroupMethod(token, appId, params.deviceGroupId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + functions: { + createFunctionInstance: (params) => + createFunctionInstanceMethod(token, appId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + getFunctionInstance: (params) => + getFunctionInstanceMethod(token, appId, params.functionInstanceId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getFunctionInstances: (params) => + getFunctionInstancesMethod(token, appId, params.params, { + ...options, + debug: resolveDebug(options?.debug), + }), + updateFunctionInstance: (params) => + updateFunctionInstanceMethod(token, appId, params.functionInstanceId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteFunctionInstance: (params) => + deleteFunctionInstanceMethod(token, appId, params.functionInstanceId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + }; + }); + + return { + data: { + ...apiResponse, + results, + }, + }; +}; + +/** + * Updates an existing Azion Edge Application. + * + * @async + * @function putApplicationMethod + * @param {string} token - The authentication token. + * @param {number} applicationId - The ID of the application to update. + * @param {ApiUpdateApplicationPayload} applicationData - The updated data for the application. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} A promise that resolves with the updated application data or an error. + */ +export const putApplicationMethod = async ( + token: string, + applicationId: number, + applicationData: ApiUpdateApplicationPayload, + options?: AzionClientOptions, +): Promise> => { + const apiResponse = await putApplicationApi( + resolveToken(token), + applicationId, + applicationData, + resolveDebug(options?.debug), + ); + + if (!apiResponse || !apiResponse.results) { + return { + error: mapApiError(apiResponse, 'put application', 'Failed to put application'), + }; + } + + const appId = apiResponse.results.id; + + const application: AzionApplication = { + ...apiResponse.results, + cache: { + createCacheSetting: (params) => + createCacheSettingMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getCacheSetting: (params) => + getCacheSettingMethod(token, appId, params.cacheSettingId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getCacheSettings: (params) => + getCacheSettingsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateCacheSetting: (params) => + updateCacheSettingMethod(token, appId, params.cacheSettingId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteCacheSetting: (params) => + deleteCacheSettingMethod(token, appId, params.cacheSettingId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + origins: { + createOrigin: (params) => + createOriginMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getOrigin: (params) => + getOriginMethod(token, appId, params.originKey, { ...options, debug: resolveDebug(options?.debug) }), + getOrigins: (params) => + getOriginsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateOrigin: (params) => + updateOriginMethod(token, appId, params.originKey, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteOrigin: (params) => + deleteOriginMethod(token, appId, params.originKey, { ...options, debug: resolveDebug(options?.debug) }), + }, + rules: { + request: { + createRule: (params) => + createRuleMethod(token, appId, 'request', params.data, { ...options, debug: resolveDebug(options?.debug) }), + getRule: (params) => + getRuleMethod(token, appId, 'request', params.ruleId, { ...options, debug: resolveDebug(options?.debug) }), + getRules: (params) => + getRulesMethod(token, appId, 'request', params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateRule: (params) => + updateRuleMethod(token, appId, 'request', params.ruleId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteRule: (params) => + deleteRuleMethod(token, appId, 'request', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + response: { + createRule: (params) => + createRuleMethod(token, appId, 'response', params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + getRule: (params) => + getRuleMethod(token, appId, 'response', params.ruleId, { ...options, debug: resolveDebug(options?.debug) }), + getRules: (params) => + getRulesMethod(token, appId, 'response', params.params, { + ...options, + debug: resolveDebug(options?.debug), + }), + updateRule: (params) => + updateRuleMethod(token, appId, 'response', params.ruleId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteRule: (params) => + deleteRuleMethod(token, appId, 'response', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + }, + devices: { + createDeviceGroup: (params) => + createDeviceGroupMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getDeviceGroup: (params) => + getDeviceGroupMethod(token, appId, params.deviceGroupId, { ...options, debug: resolveDebug(options?.debug) }), + getDeviceGroups: (params) => + getDeviceGroupsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateDeviceGroup: (params) => + updateDeviceGroupMethod(token, appId, params.deviceGroupId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteDeviceGroup: (params) => + deleteDeviceGroupMethod(token, appId, params.deviceGroupId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + functions: { + createFunctionInstance: (params) => + createFunctionInstanceMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getFunctionInstance: (params) => + getFunctionInstanceMethod(token, appId, params.functionInstanceId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getFunctionInstances: (params) => + getFunctionInstancesMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateFunctionInstance: (params) => + updateFunctionInstanceMethod(token, appId, params.functionInstanceId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteFunctionInstance: (params) => + deleteFunctionInstanceMethod(token, appId, params.functionInstanceId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + }; + + return { data: application }; +}; + +/** + * Partially updates an existing Azion Edge Application. + * + * @async + * @function patchApplicationMethod + * @param {string} token - The authentication token. + * @param {number} applicationId - The ID of the application to patch. + * @param {Partial} applicationData - The partial data to update in the application. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} A promise that resolves with the patched application data or an error. + */ +export const patchApplicationMethod = async ( + token: string, + applicationId: number, + applicationData: Partial, + options?: AzionClientOptions, +): Promise> => { + const apiResponse = await patchApplicationApi( + resolveToken(token), + applicationId, + applicationData, + resolveDebug(options?.debug), + ); + + if (!apiResponse || !apiResponse.results) { + return { + error: mapApiError(apiResponse, 'patch application', 'Failed to patch application'), + }; + } + + const appId = apiResponse.results.id; + + const application: AzionApplication = { + ...apiResponse.results, + cache: { + createCacheSetting: (params) => + createCacheSettingMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getCacheSetting: (params) => + getCacheSettingMethod(token, appId, params.cacheSettingId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getCacheSettings: (params) => + getCacheSettingsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateCacheSetting: (params) => + updateCacheSettingMethod(token, appId, params.cacheSettingId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteCacheSetting: (params) => + deleteCacheSettingMethod(token, appId, params.cacheSettingId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + origins: { + createOrigin: (params) => + createOriginMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getOrigin: (params) => + getOriginMethod(token, appId, params.originKey, { ...options, debug: resolveDebug(options?.debug) }), + getOrigins: (params) => + getOriginsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateOrigin: (params) => + updateOriginMethod(token, appId, params.originKey, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteOrigin: (params) => + deleteOriginMethod(token, appId, params.originKey, { ...options, debug: resolveDebug(options?.debug) }), + }, + rules: { + request: { + createRule: (params) => + createRuleMethod(token, appId, 'request', params.data, { ...options, debug: resolveDebug(options?.debug) }), + getRule: (params) => + getRuleMethod(token, appId, 'request', params.ruleId, { ...options, debug: resolveDebug(options?.debug) }), + getRules: (params) => + getRulesMethod(token, appId, 'request', params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateRule: (params) => + updateRuleMethod(token, appId, 'request', params.ruleId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteRule: (params) => + deleteRuleMethod(token, appId, 'request', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + response: { + createRule: (params) => + createRuleMethod(token, appId, 'response', params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + getRule: (params) => + getRuleMethod(token, appId, 'response', params.ruleId, { ...options, debug: resolveDebug(options?.debug) }), + getRules: (params) => + getRulesMethod(token, appId, 'response', params.params, { + ...options, + debug: resolveDebug(options?.debug), + }), + updateRule: (params) => + updateRuleMethod(token, appId, 'response', params.ruleId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteRule: (params) => + deleteRuleMethod(token, appId, 'response', params.ruleId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + }, + devices: { + createDeviceGroup: (params) => + createDeviceGroupMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getDeviceGroup: (params) => + getDeviceGroupMethod(token, appId, params.deviceGroupId, { ...options, debug: resolveDebug(options?.debug) }), + getDeviceGroups: (params) => + getDeviceGroupsMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateDeviceGroup: (params) => + updateDeviceGroupMethod(token, appId, params.deviceGroupId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteDeviceGroup: (params) => + deleteDeviceGroupMethod(token, appId, params.deviceGroupId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + functions: { + createFunctionInstance: (params) => + createFunctionInstanceMethod(token, appId, params.data, { ...options, debug: resolveDebug(options?.debug) }), + getFunctionInstance: (params) => + getFunctionInstanceMethod(token, appId, params.functionInstanceId, { + ...options, + debug: resolveDebug(options?.debug), + }), + getFunctionInstances: (params) => + getFunctionInstancesMethod(token, appId, params.params, { ...options, debug: resolveDebug(options?.debug) }), + updateFunctionInstance: (params) => + updateFunctionInstanceMethod(token, appId, params.functionInstanceId, params.data, { + ...options, + debug: resolveDebug(options?.debug), + }), + deleteFunctionInstance: (params) => + deleteFunctionInstanceMethod(token, appId, params.functionInstanceId, { + ...options, + debug: resolveDebug(options?.debug), + }), + }, + }; + + return { data: application }; +}; + +/** + * Deletes an Azion Edge Application. + * + * @async + * @function deleteApplicationMethod + * @param {string} token - The authentication token. + * @param {number} applicationId - The ID of the application to delete. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} A promise that resolves when the application is deleted or rejects with an error. + */ +export const deleteApplicationMethod = async ( + token: string, + applicationId: number, + options?: AzionClientOptions, +): Promise> => { + try { + await deleteApplicationApi(resolveToken(token), applicationId, resolveDebug(options?.debug)); + return { data: undefined }; + } catch (error) { + return { + error: mapApiError(error, 'delete application', 'Failed to delete application'), + }; + } +}; + +/** + * Creates a new Azion Edge Application. + * + * @async + * @function + * @param {Object} params - The parameters for creating an application. + * @param {ApiCreateApplicationPayload} params.data - The data for the new application. + * @param {AzionClientOptions} [params.options] - Optional client options. + * @returns {Promise>} A promise that resolves with the created application data or an error. + * + * @example + * const result = await createApplication({ + * data: { name: 'My New App', delivery_protocol: 'http' }, + * options: { debug: true } + * }); + * if (result.data) { + * console.log('Application created:', result.data); + * } else { + * console.error('Error:', result.error); + * } + */ +export const createApplicationWrapper = ({ + data, + options, +}: { + data: ApiCreateApplicationPayload; + options?: AzionClientOptions; +}): Promise> => createApplicationMethod(resolveToken(), data, options); + +/** + * Retrieves a specific Azion Edge Application by ID. + * + * @async + * @function + * @param {Object} params - The parameters for retrieving an application. + * @param {number} params.applicationId - The ID of the application to retrieve. + * @param {AzionClientOptions} [params.options] - Optional client options. + * @returns {Promise>} A promise that resolves with the application data or an error. + * + * @example + * const result = await getApplication({ + * applicationId: 123, + * options: { debug: true } + * }); + * if (result.data) { + * console.log('Application retrieved:', result.data); + * } else { + * console.error('Error:', result.error); + * } + */ +export const getApplicationWrapper = ({ + applicationId, + options, +}: { + applicationId: number; + options?: AzionClientOptions; +}): Promise> => getApplicationMethod(resolveToken(), applicationId, options); + +/** + * Retrieves a list of Azion Edge Applications. + * + * @async + * @function + * @param {Object} [params] - The parameters for retrieving applications. + * @param {ApiListApplicationsParams} [params.params] - Optional parameters for filtering and pagination. + * @param {AzionClientOptions} [params.options] - Optional client options. + * @returns {Promise>} A promise that resolves with a collection of applications or an error. + * + * @example + * const result = await getApplicationsWrapper(); + * // ou + * const result = await getApplicationsWrapper({ + * params: { page: 1, page_size: 20 }, + * options: { debug: true } + * }); + * if (result.data) { + * console.log('Applications retrieved:', result.data.results); + * } else { + * console.error('Error:', result.error); + * } + */ +export const getApplicationsWrapper = (params?: { + params?: ApiListApplicationsParams; + options?: AzionClientOptions; +}): Promise> => + getApplicationsMethod(resolveToken(), params?.params, params?.options); + +/** + * Updates an existing Azion Edge Application. + * + * @async + * @function + * @param {Object} params - The parameters for updating an application. + * @param {number} params.applicationId - The ID of the application to update. + * @param {ApiUpdateApplicationPayload} params.data - The updated data for the application. + * @param {AzionClientOptions} [params.options] - Optional client options. + * @returns {Promise>} A promise that resolves with the updated application data or an error. + * + * @example + * const result = await putApplication({ + * applicationId: 123, + * data: { name: 'Updated App Name' }, + * options: { debug: true } + * }); + * if (result.data) { + * console.log('Application updated:', result.data); + * } else { + * console.error('Error:', result.error); + * } + */ +export const putApplicationWrapper = ({ + applicationId, + data, + options, +}: { + applicationId: number; + data: ApiUpdateApplicationPayload; + options?: AzionClientOptions; +}): Promise> => + putApplicationMethod(resolveToken(), applicationId, data, options); + +/** + * Partially updates an existing Azion Edge Application. + * + * @async + * @function + * @param {Object} params - The parameters for patching an application. + * @param {number} params.applicationId - The ID of the application to patch. + * @param {Partial} params.data - The partial data to update in the application. + * @param {AzionClientOptions} [params.options] - Optional client options. + * @returns {Promise>} A promise that resolves with the patched application data or an error. + * + * @example + * const result = await patchApplication({ + * applicationId: 123, + * data: { delivery_protocol: 'https' }, + * options: { debug: true } + * }); + * if (result.data) { + * console.log('Application patched:', result.data); + * } else { + * console.error('Error:', result.error); + * } + */ +export const patchApplicationWrapper = ({ + applicationId, + data, + options, +}: { + applicationId: number; + data: Partial; + options?: AzionClientOptions; +}): Promise> => + patchApplicationMethod(resolveToken(), applicationId, data, options); + +/** + * Deletes an Azion Edge Application. + * + * @async + * @function + * @param {Object} params - The parameters for deleting an application. + * @param {number} params.applicationId - The ID of the application to delete. + * @param {AzionClientOptions} [params.options] - Optional client options. + * @returns {Promise>} A promise that resolves when the application is deleted or rejects with an error. + * + * @example + * const result = await deleteApplication({ + * applicationId: 123, + * options: { debug: true } + * }); + * if (result.data !== undefined) { + * console.log('Application deleted successfully'); + * } else { + * console.error('Error:', result.error); + * } + */ +export const deleteApplicationWrapper = ({ + applicationId, + options, +}: { + applicationId: number; + options?: AzionClientOptions; +}): Promise> => deleteApplicationMethod(resolveToken(), applicationId, options); diff --git a/packages/applications/src/main-settings/services/index.ts b/packages/applications/src/main-settings/services/index.ts new file mode 100644 index 0000000..f804266 --- /dev/null +++ b/packages/applications/src/main-settings/services/index.ts @@ -0,0 +1,194 @@ +import { + ApiCreateApplicationPayload, + ApiCreateApplicationResponse, + ApiDeleteApplicationResponse, + ApiGetApplicationResponse, + ApiListApplicationsParams, + ApiListApplicationsResponse, + ApiUpdateApplicationPayload, + ApiUpdateApplicationResponse, +} from './types'; + +const BASE_URL = 'https://api.azionapi.net/edge_applications'; + +/** + * Lists all edge applications with optional filtering and pagination. + * + * @param {string} token - Authentication token for Azion API. + * @param {ApiListApplicationsParams} [params] - Optional parameters for filtering and pagination. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Array of applications or an error if retrieval failed. + */ +const getApplications = async ( + token: string, + params?: ApiListApplicationsParams, + debug?: boolean, +): Promise => { + try { + const { order_by = 'name', sort = 'asc', page = 1, page_size = 10 } = params || {}; + const queryParams = new URLSearchParams({ + order_by, + sort, + page: String(page), + page_size: String(page_size), + }); + const response = await fetch(`${BASE_URL}?${queryParams.toString()}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error getting all applications:', error); + throw error; + } +}; + +/** + * Retrieves edge application settings by ID. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Application data or an error if retrieval failed. + */ +const getApplicationById = async (token: string, Id: number, debug?: boolean): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error getting application by ID:', error); + throw error; + } +}; + +/** + * Creates a new edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {ApiCreateApplicationPayload} applicationData - Data of the application to be created. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The created application or an error if creation failed. + */ +const postApplication = async ( + token: string, + applicationData: ApiCreateApplicationPayload, + debug?: boolean, +): Promise => { + try { + const response = await fetch(BASE_URL, { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(applicationData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error creating application:', error); + throw error; + } +}; + +/** + * Overwrites edge application settings. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application to update. + * @param {ApiUpdateApplicationPayload} applicationData - New data for the application. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The updated application or an error if update failed. + */ +const putApplication = async ( + token: string, + Id: number, + applicationData: ApiUpdateApplicationPayload, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}`, { + method: 'PUT', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(applicationData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error updating application:', error); + throw error; + } +}; + +/** + * Partially updates edge application settings. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application to update. + * @param {Partial} applicationData - Partial data for the application update. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The updated application or an error if update failed. + */ +const patchApplication = async ( + token: string, + Id: number, + applicationData: Partial, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}`, { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(applicationData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error patching application:', error); + throw error; + } +}; + +/** + * Deletes an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application to delete. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Confirmation of deletion or an error if deletion failed. + */ +const deleteApplication = async (token: string, Id: number, debug?: boolean): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}`, { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error deleting application:', error); + throw error; + } +}; + +export { deleteApplication, getApplicationById, getApplications, patchApplication, postApplication, putApplication }; diff --git a/packages/applications/src/main-settings/services/types.ts b/packages/applications/src/main-settings/services/types.ts new file mode 100644 index 0000000..9edfedb --- /dev/null +++ b/packages/applications/src/main-settings/services/types.ts @@ -0,0 +1,102 @@ +export enum DeliveryProtocol { + HTTP = 'http', + HTTPS = 'https', + HTTP_HTTPS = 'http,https', +} + +export enum HttpPort { + PORT_80 = 80, + PORT_8008 = 8008, + PORT_8080 = 8080, +} + +export enum HttpsPort { + PORT_443 = 443, + PORT_8443 = 8443, + PORT_9440 = 9440, + PORT_9441 = 9441, + PORT_9442 = 9442, + PORT_9443 = 9443, +} + +export enum TlsVersion { + TLS_1_0 = 'tls_1_0', + TLS_1_1 = 'tls_1_1', + TLS_1_2 = 'tls_1_2', + TLS_1_3 = 'tls_1_3', +} + +export enum SupportedCiphers { + ALL = 'all', + TLSv1_2_2018 = 'TLSv1.2_2018', + TLSv1_2_2019 = 'TLSv1.2_2019', + TLSv1_2_2021 = 'TLSv1.2_2021', + TLSv1_3_2022 = 'TLSv1.3_2022', +} + +export interface ApiBaseApplicationPayload { + name: string; + delivery_protocol?: DeliveryProtocol; + http3?: boolean; + http_port?: HttpPort[]; + https_port?: HttpsPort[]; + minimum_tls_version?: TlsVersion; + active?: boolean; + debug_rules?: boolean; + application_acceleration?: boolean; + caching?: boolean; + device_detection?: boolean; + edge_firewall?: boolean; + edge_functions?: boolean; + image_optimization?: boolean; + l2_caching?: boolean; + load_balancer?: boolean; + raw_logs?: boolean; + web_application_firewall?: boolean; + supported_ciphers?: SupportedCiphers; +} + +export interface ApiCreateApplicationPayload extends ApiBaseApplicationPayload {} + +export interface ApiApplication extends ApiBaseApplicationPayload { + id: number; +} + +export interface ApiListApplicationsParams { + page?: number; + page_size?: number; + sort?: 'name' | 'id'; + order_by?: string; +} + +export interface ApiListApplicationsResponse { + count: number; + total_pages: number; + schema_version: number; + links: { + previous: string | null; + next: string | null; + }; + results: ApiApplication[]; +} + +export interface ApiGetApplicationResponse { + results: ApiApplication; + schema_version: number; +} + +export interface ApiCreateApplicationResponse { + results: ApiApplication; + schema_version: number; +} + +export interface ApiUpdateApplicationPayload extends Partial {} + +export interface ApiUpdateApplicationResponse { + results: ApiApplication; + schema_version: number; +} + +export interface ApiDeleteApplicationResponse { + schema_version: number; +} diff --git a/packages/applications/src/main-settings/types.ts b/packages/applications/src/main-settings/types.ts new file mode 100644 index 0000000..69aeba2 --- /dev/null +++ b/packages/applications/src/main-settings/types.ts @@ -0,0 +1,24 @@ +import { + ApiApplication, + ApiBaseApplicationPayload, + ApiListApplicationsParams, + ApiUpdateApplicationPayload, + DeliveryProtocol, + HttpPort, + HttpsPort, + SupportedCiphers, + TlsVersion, +} from './services/types'; + +export type AzionApplicationSettings = ApiApplication; + +export { + ApiBaseApplicationPayload, + ApiListApplicationsParams, + ApiUpdateApplicationPayload, + DeliveryProtocol, + HttpPort, + HttpsPort, + SupportedCiphers, + TlsVersion, +}; diff --git a/packages/applications/src/origins/index.ts b/packages/applications/src/origins/index.ts new file mode 100644 index 0000000..e869344 --- /dev/null +++ b/packages/applications/src/origins/index.ts @@ -0,0 +1,270 @@ +import { AzionApplicationCollectionResponse, AzionApplicationResponse, AzionClientOptions } from '../types'; +import { resolveDebug, resolveToken } from '../utils'; +import { createOrigin, deleteOrigin, getOriginByKey, listOrigins, updateOrigin } from './services/index'; +import { ApiCreateOriginPayload, ApiListOriginsParams, ApiUpdateOriginRequest } from './services/types'; +import { AzionOrigin } from './types'; + +export const createOriginMethod = async ( + token: string, + Id: number, + originData: ApiCreateOriginPayload, + options?: AzionClientOptions, +): Promise> => { + try { + const { results } = await createOrigin(token, Id, originData, resolveDebug(options?.debug)); + return { data: results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to create origin', + operation: 'create origin', + }, + }; + } +}; + +export const deleteOriginMethod = async ( + token: string, + Id: number, + originKey: string, + options?: AzionClientOptions, +): Promise> => { + try { + await deleteOrigin(token, Id, originKey, resolveDebug(options?.debug)); + return { data: undefined }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to delete origin', + operation: 'delete origin', + }, + }; + } +}; + +export const getOriginMethod = async ( + token: string, + Id: number, + originKey: string, + options?: AzionClientOptions, +): Promise> => { + try { + const { results } = await getOriginByKey(token, Id, originKey, resolveDebug(options?.debug)); + return { data: results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to get origin', + operation: 'get origin', + }, + }; + } +}; + +export const getOriginsMethod = async ( + token: string, + Id: number, + params?: ApiListOriginsParams, + options?: AzionClientOptions, +): Promise> => { + try { + const data = await listOrigins(token, Id, params, resolveDebug(options?.debug)); + return { data }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to get origins', + operation: 'get origins', + }, + }; + } +}; + +export const updateOriginMethod = async ( + token: string, + Id: number, + originKey: string, + originData: ApiUpdateOriginRequest, + options?: AzionClientOptions, +): Promise> => { + try { + const { results } = await updateOrigin(token, Id, originKey, originData, resolveDebug(options?.debug)); + return { data: results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to update origin', + operation: 'update origin', + }, + }; + } +}; + +/** + * Creates a new origin for a specific application. + * + * @param {Object} params - Parameters for creating an origin. + * @param {number} params.applicationId - Application ID. + * @param {ApiCreateOriginPayload} params.data - Data for the origin to be created. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The created origin or an error. + * + * @example + * const { error, data } = await createOrigin({ + * applicationId: 1234, + * data: { + * name: 'My New Origin', + * addresses: [{ address: 'example.com' }], + * origin_type: 'single_origin' + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to create origin:', error); + * } else { + * console.log('Origin created:', data); + * } + */ +export const createOriginWrapper = ({ + applicationId, + data, + options, +}: { + applicationId: number; + data: ApiCreateOriginPayload; + options?: AzionClientOptions; +}): Promise> => createOriginMethod(resolveToken(), applicationId, data, options); + +/** + * Deletes a specific origin from an application. + * + * @param {Object} params - Parameters for deleting an origin. + * @param {number} params.applicationId - Application ID. + * @param {string} params.originKey - Key of the origin to delete. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} A response indicating success or an error. + * + * @example + * const { error, data } = await deleteOrigin({ + * applicationId: 1234, + * originKey: 'origin-key', + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to delete origin:', error); + * } else { + * console.log('Origin deleted successfully'); + * } + */ +export const deleteOriginWrapper = ({ + applicationId, + originKey, + options, +}: { + applicationId: number; + originKey: string; + options?: AzionClientOptions; +}): Promise> => deleteOriginMethod(resolveToken(), applicationId, originKey, options); + +/** + * Retrieves a specific origin from an application. + * + * @param {Object} params - Parameters for retrieving an origin. + * @param {number} params.applicationId - Application ID. + * @param {string} params.originKey - Key of the origin to retrieve. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The retrieved origin or an error. + * + * @example + * const { error, data } = await getOrigin({ + * applicationId: 1234, + * originKey: 'origin-key', + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to get origin:', error); + * } else { + * console.log('Retrieved origin:', data); + * } + */ +export const getOriginWrapper = ({ + applicationId, + originKey, + options, +}: { + applicationId: number; + originKey: string; + options?: AzionClientOptions; +}): Promise> => + getOriginMethod(resolveToken(), applicationId, originKey, options); + +/** + * Retrieves a list of origins for a specific application. + * + * @param {Object} params - Parameters for listing origins. + * @param {number} params.applicationId - Application ID. + * @param {ApiListOriginsParams} [params.params] - Parameters for filtering and pagination. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} A collection of origins or an error. + * + * @example + * const { error, data } = await getOrigins({ + * applicationId: 1234, + * params: { page: 1, page_size: 20 }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to get origins:', error); + * } else { + * console.log('Retrieved origins:', data.results); + * } + */ +export const getOriginsWrapper = ({ + applicationId, + params, + options, +}: { + applicationId: number; + params?: ApiListOriginsParams; + options?: AzionClientOptions; +}): Promise> => + getOriginsMethod(resolveToken(), applicationId, params, options); + +/** + * Updates an existing origin in a specific application. + * + * @param {Object} params - Parameters for updating an origin. + * @param {number} params.applicationId - Application ID. + * @param {string} params.originKey - Key of the origin to update. + * @param {ApiUpdateOriginRequest} params.data - Updated data for the origin. + * @param {AzionClientOptions} [params.options] - Client options including debug mode. + * @returns {Promise>} The updated origin or an error. + * + * @example + * const { error, data } = await updateOrigin({ + * applicationId: 1234, + * originKey: 'origin-key', + * data: { + * name: 'Updated Origin', + * addresses: [{ address: 'updated-example.com' }] + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Failed to update origin:', error); + * } else { + * console.log('Updated origin:', data); + * } + */ +export const updateOriginWrapper = ({ + applicationId, + originKey, + data, + options, +}: { + applicationId: number; + originKey: string; + data: ApiUpdateOriginRequest; + options?: AzionClientOptions; +}): Promise> => + updateOriginMethod(resolveToken(), applicationId, originKey, data, options); diff --git a/packages/applications/src/origins/services/index.ts b/packages/applications/src/origins/services/index.ts new file mode 100644 index 0000000..6cb587a --- /dev/null +++ b/packages/applications/src/origins/services/index.ts @@ -0,0 +1,179 @@ +import { + ApiCreateOriginPayload, + ApiCreateOriginResponse, + ApiDeleteOriginResponse, + ApiGetOriginResponse, + ApiListOriginsParams, + ApiListOriginsResponse, + ApiUpdateOriginRequest, + ApiUpdateOriginResponse, +} from './types'; + +const BASE_URL = 'https://api.azionapi.net/edge_applications'; + +/** + * Lists all origins for an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {ApiListOriginsParams} [params] - Optional parameters for filtering and pagination. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Array of origins or an error if retrieval failed. + */ +const listOrigins = async ( + token: string, + Id: number, + params?: ApiListOriginsParams, + debug?: boolean, +): Promise => { + try { + const { page = 1, page_size = 10, sort = 'name', order = 'asc', filter = '' } = params || {}; + const queryParams = new URLSearchParams({ + page: String(page), + page_size: String(page_size), + sort, + order, + filter, + }); + const response = await fetch(`${BASE_URL}/${Id}/origins?${queryParams.toString()}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error listing origins:', error); + throw error; + } +}; + +/** + * Retrieves a specific origin by key. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {string} originKey - Key of the origin. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Origin data or an error if retrieval failed. + */ +const getOriginByKey = async ( + token: string, + Id: number, + originKey: string, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/origins/${originKey}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error getting origin by key:', error); + throw error; + } +}; + +/** + * Creates a new origin for an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {ApiCreateOriginPayload} originData - Data of the origin to be created. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The created origin or an error if creation failed. + */ +const createOrigin = async ( + token: string, + Id: number, + originData: ApiCreateOriginPayload, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/origins`, { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(originData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error creating origin:', error); + throw error; + } +}; + +/** + * Updates an existing origin. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {string} originKey - Key of the origin to update. + * @param {ApiUpdateOriginRequest} originData - New data for the origin. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The updated origin or an error if update failed. + */ +const updateOrigin = async ( + token: string, + Id: number, + originKey: string, + originData: ApiUpdateOriginRequest, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/origins/${originKey}`, { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(originData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error updating origin:', error); + throw error; + } +}; + +/** + * Deletes an origin. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {string} originKey - Key of the origin to delete. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Confirmation of deletion or an error if deletion failed. + */ +const deleteOrigin = async ( + token: string, + Id: number, + originKey: string, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/origins/${originKey}`, { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error deleting origin:', error); + throw error; + } +}; + +export { createOrigin, deleteOrigin, getOriginByKey, listOrigins, updateOrigin }; diff --git a/packages/applications/src/origins/services/types.ts b/packages/applications/src/origins/services/types.ts new file mode 100644 index 0000000..b54432d --- /dev/null +++ b/packages/applications/src/origins/services/types.ts @@ -0,0 +1,96 @@ +export interface ApiListOriginsParams { + page?: number; + page_size?: number; + sort?: 'name' | 'id'; + order?: OrderDirection; + filter?: string; +} + +export interface ApiListOriginsResponse { + count: number; + total_pages: number; + schema_version: number; + links: { + previous: string | null; + next: string | null; + }; + results: ApiOrigin[]; +} + +export interface ApiGetOriginResponse { + results: ApiOrigin; + schema_version: number; +} + +export enum OrderDirection { + ASC = 'asc', + DESC = 'desc', +} + +export enum OriginType { + SINGLE_ORIGIN = 'single_origin', + LOAD_BALANCER = 'load_balancer', +} + +export enum OriginProtocolPolicy { + PRESERVE = 'preserve', + HTTP = 'http', + HTTPS = 'https', +} + +export enum ServerRole { + PRIMARY = 'primary', + BACKUP = 'backup', +} + +export interface Address { + address: string; + weight?: number | null; + server_role?: ServerRole; + is_active?: boolean; +} + +export interface ApiOrigin { + id: number; + origin_key: string; + name: string; + origin_type: OriginType; + addresses: Address[]; + origin_protocol_policy: OriginProtocolPolicy; + is_origin_redirection_enabled: boolean; + host_header: string; + method: string; + origin_path: string; + connection_timeout: number; + timeout_between_bytes: number; + hmac_authentication: boolean; + hmac_region_name: string; + hmac_access_key: string; + hmac_secret_key: string; +} + +export type ApiCreateOriginPayload = Omit & { + origin_path?: string; + hmac_authentication?: boolean; + hmac_region_name?: string; + hmac_access_key?: string; + hmac_secret_key?: string; + connection_timeout?: number; + timeout_between_bytes?: number; +}; + +export type ApiUpdateOriginRequest = Partial & { id: number }; + +export interface ApiCreateOriginResponse { + results: ApiOrigin; + schema_version: number; +} + +export interface ApiUpdateOriginResponse { + results: ApiOrigin; + schema_version: number; +} + +export interface ApiDeleteOriginResponse { + schema_version: number; +} diff --git a/packages/applications/src/origins/types.ts b/packages/applications/src/origins/types.ts new file mode 100644 index 0000000..22c76e9 --- /dev/null +++ b/packages/applications/src/origins/types.ts @@ -0,0 +1,5 @@ +import { ApiOrigin } from './services/types'; + +export type AzionOrigin = ApiOrigin; + +export { ApiCreateOriginPayload, ApiListOriginsParams, ApiUpdateOriginRequest } from './services/types'; diff --git a/packages/applications/src/rules-engine/index.ts b/packages/applications/src/rules-engine/index.ts new file mode 100644 index 0000000..f8b5de5 --- /dev/null +++ b/packages/applications/src/rules-engine/index.ts @@ -0,0 +1,367 @@ +import { AzionApplicationCollectionResponse, AzionApplicationResponse, AzionClientOptions } from '../types'; +import { resolveDebug, resolveToken } from '../utils'; +import { createRule, deleteRule, getRuleById, listRules, updateRule } from './services/index'; +import { ApiCreateRulePayload, ApiListRulesParams, ApiUpdateRulePayload } from './services/types'; +import { AzionRule } from './types'; + +/** + * Creates a new rule for an Azion Edge Application. + * + * @async + * @function + * @param {Object} params - The parameters for creating a rule. + * @param {number} params.applicationId - The ID of the application to create the rule for. + * @param {'request' | 'response'} params.phase - The phase of the rule (request or response). + * @param {ApiCreateRulePayload} params.data - The data for the new rule. + * @param {AzionClientOptions} [params.options] - Optional client options. + * @returns {Promise>} A promise that resolves with the created rule data or an error. + * + * @example + * const { error, data } = await createRule({ + * applicationId: 123, + * phase: 'request', + * data: { name: 'My New Rule', behaviors: [...] }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Rule created:', data); + * } + */ +export const createRuleWrapper = ({ + applicationId, + phase, + data, + options, +}: { + applicationId: number; + phase: 'request' | 'response'; + data: ApiCreateRulePayload; + options?: AzionClientOptions; +}): Promise> => + createRuleMethod(resolveToken(), applicationId, phase, data, options); + +/** + * Retrieves a specific rule from an Azion Edge Application. + * + * @async + * @function + * @param {Object} params - The parameters for retrieving a rule. + * @param {number} params.applicationId - The ID of the application containing the rule. + * @param {'request' | 'response'} params.phase - The phase of the rule (request or response). + * @param {number} params.ruleId - The ID of the rule to retrieve. + * @param {AzionClientOptions} [params.options] - Optional client options. + * @returns {Promise>} A promise that resolves with the rule data or an error. + * + * @example + * const { error, data } = await getRule({ + * applicationId: 123, + * phase: 'request', + * ruleId: 456, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Rule retrieved:', data); + * } + */ +export const getRuleWrapper = ({ + applicationId, + phase, + ruleId, + options, +}: { + applicationId: number; + phase: 'request' | 'response'; + ruleId: number; + options?: AzionClientOptions; +}): Promise> => + getRuleMethod(resolveToken(), applicationId, phase, ruleId, options); + +/** + * Retrieves a list of rules for an Azion Edge Application. + * + * @async + * @function + * @param {Object} params - The parameters for retrieving rules. + * @param {number} params.applicationId - The ID of the application to retrieve rules from. + * @param {'request' | 'response'} params.phase - The phase of the rules to retrieve (request or response). + * @param {ApiListRulesParams} [params.params] - Optional parameters for filtering and pagination. + * @param {AzionClientOptions} [params.options] - Optional client options. + * @returns {Promise>} A promise that resolves with a collection of rules or an error. + * + * @example + * const { error, data } = await getRules({ + * applicationId: 123, + * phase: 'request', + * params: { page: 1, page_size: 20 }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Rules retrieved:', data.results); + * } + */ +export const getRulesWrapper = ({ + applicationId, + phase, + params, + options, +}: { + applicationId: number; + phase: 'request' | 'response'; + params?: ApiListRulesParams; + options?: AzionClientOptions; +}): Promise> => + getRulesMethod(resolveToken(), applicationId, phase, params, options); + +/** + * Updates an existing rule in an Azion Edge Application. + * + * @async + * @function + * @param {Object} params - The parameters for updating a rule. + * @param {number} params.applicationId - The ID of the application containing the rule. + * @param {'request' | 'response'} params.phase - The phase of the rule (request or response). + * @param {number} params.ruleId - The ID of the rule to update. + * @param {ApiUpdateRulePayload} params.data - The updated data for the rule. + * @param {AzionClientOptions} [params.options] - Optional client options. + * @returns {Promise>} A promise that resolves with the updated rule data or an error. + * + * @example + * const { error, data } = await updateRule({ + * applicationId: 123, + * phase: 'request', + * ruleId: 456, + * data: { name: 'Updated Rule Name', behaviors: [...] }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Rule updated:', data); + * } + */ +export const updateRuleWrapper = ({ + applicationId, + phase, + ruleId, + data, + options, +}: { + applicationId: number; + phase: 'request' | 'response'; + ruleId: number; + data: ApiUpdateRulePayload; + options?: AzionClientOptions; +}): Promise> => + updateRuleMethod(resolveToken(), applicationId, phase, ruleId, data, options); + +/** + * Deletes a rule from an Azion Edge Application. + * + * @async + * @function + * @param {Object} params - The parameters for deleting a rule. + * @param {number} params.applicationId - The ID of the application containing the rule. + * @param {'request' | 'response'} params.phase - The phase of the rule (request or response). + * @param {number} params.ruleId - The ID of the rule to delete. + * @param {AzionClientOptions} [params.options] - Optional client options. + * @returns {Promise>} A promise that resolves when the rule is deleted or rejects with an error. + * + * @example + * const { error, data } = await deleteRule({ + * applicationId: 123, + * phase: 'request', + * ruleId: 456, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Rule deleted successfully'); + * } + */ +export const deleteRuleWrapper = ({ + applicationId, + phase, + ruleId, + options, +}: { + applicationId: number; + phase: 'request' | 'response'; + ruleId: number; + options?: AzionClientOptions; +}): Promise> => deleteRuleMethod(resolveToken(), applicationId, phase, ruleId, options); + +/** + * Internal method to create a new rule. + * + * @async + * @function + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - The ID of the application. + * @param {'request' | 'response'} phase - The phase of the rule (request or response). + * @param {ApiCreateRulePayload} ruleData - The data for the new rule. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} A promise that resolves with the created rule data or an error. + */ +export const createRuleMethod = async ( + token: string, + Id: number, + phase: 'request' | 'response', + ruleData: ApiCreateRulePayload, + options?: AzionClientOptions, +): Promise> => { + try { + const { results } = await createRule(resolveToken(token), Id, phase, ruleData, resolveDebug(options?.debug)); + return { data: results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to create rule', + operation: 'create rule', + }, + }; + } +}; + +/** + * Internal method to retrieve a specific rule. + * + * @async + * @function + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - The ID of the application. + * @param {'request' | 'response'} phase - The phase of the rule (request or response). + * @param {number} ruleId - The ID of the rule to retrieve. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} A promise that resolves with the rule data or an error. + */ +export const getRuleMethod = async ( + token: string, + Id: number, + phase: 'request' | 'response', + ruleId: number, + options?: AzionClientOptions, +): Promise> => { + try { + const { results } = await getRuleById(resolveToken(token), Id, phase, ruleId, resolveDebug(options?.debug)); + return { data: results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to get rule', + operation: 'get rule', + }, + }; + } +}; + +/** + * Internal method to retrieve a list of rules. + * + * @async + * @function + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - The ID of the application. + * @param {'request' | 'response'} phase - The phase of the rules to retrieve (request or response). + * @param {ApiListRulesParams} [params] - Optional parameters for filtering and pagination. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} A promise that resolves with a collection of rules or an error. + */ +export const getRulesMethod = async ( + token: string, + Id: number, + phase: 'request' | 'response', + params?: ApiListRulesParams, + options?: AzionClientOptions, +): Promise> => { + try { + const data = await listRules(resolveToken(token), Id, phase, params, resolveDebug(options?.debug)); + return { data }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to get rules', + operation: 'get rules', + }, + }; + } +}; + +/** + * Internal method to update an existing rule. + * + * @async + * @function + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - The ID of the application. + * @param {'request' | 'response'} phase - The phase of the rule (request or response). + * @param {number} ruleId - The ID of the rule to update. + * @param {ApiUpdateRulePayload} ruleData - The updated data for the rule. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} A promise that resolves with the updated rule data or an error. + */ +export const updateRuleMethod = async ( + token: string, + Id: number, + phase: 'request' | 'response', + ruleId: number, + ruleData: ApiUpdateRulePayload, + options?: AzionClientOptions, +): Promise> => { + try { + const { results } = await updateRule( + resolveToken(token), + Id, + phase, + ruleId, + ruleData, + resolveDebug(options?.debug), + ); + return { data: results }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to update rule', + operation: 'update rule', + }, + }; + } +}; + +/** + * Internal method to delete a rule. + * + * @async + * @function + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - The ID of the application. + * @param {'request' | 'response'} phase - The phase of the rule (request or response). + * @param {number} ruleId - The ID of the rule to delete. + * @param {AzionClientOptions} [options] - Optional client options. + * @returns {Promise>} A promise that resolves when the rule is deleted or rejects with an error. + */ +export const deleteRuleMethod = async ( + token: string, + Id: number, + phase: 'request' | 'response', + ruleId: number, + options?: AzionClientOptions, +): Promise> => { + try { + await deleteRule(resolveToken(token), Id, phase, ruleId, resolveDebug(options?.debug)); + return { data: undefined }; + } catch (error) { + return { + error: { + message: error instanceof Error ? error.message : 'Failed to delete rule', + operation: 'delete rule', + }, + }; + } +}; diff --git a/packages/applications/src/rules-engine/services/index.ts b/packages/applications/src/rules-engine/services/index.ts new file mode 100644 index 0000000..b31e28d --- /dev/null +++ b/packages/applications/src/rules-engine/services/index.ts @@ -0,0 +1,178 @@ +import { + ApiCreateRulePayload, + ApiListRulesParams, + ApiListRulesResponse, + ApiRuleResponse, + ApiUpdateRulePayload, +} from './types'; + +const BASE_URL = 'https://api.azionapi.net/edge_applications'; + +/** + * Lists all rules for a specific phase (request or response) of an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {'request' | 'response'} phase - The phase of the rules. + * @param {ApiListRulesParams} [params] - Optional parameters for filtering and pagination. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Array of rules or an error if retrieval failed. + */ +export const listRules = async ( + token: string, + Id: number, + phase: 'request' | 'response', + params?: ApiListRulesParams, + debug?: boolean, +): Promise => { + try { + const { page = 1, page_size = 10, sort = 'name', order = 'asc' } = params || {}; + const queryParams = new URLSearchParams({ + page: String(page), + page_size: String(page_size), + sort, + order, + }); + const response = await fetch(`${BASE_URL}/${Id}/rules_engine/${phase}/rules?${queryParams.toString()}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error listing rules:', error); + throw error; + } +}; +/** + * Retrieves a specific rule by its ID for a given phase of an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {'request' | 'response'} phase - The phase of the rule. + * @param {number} ruleId - ID of the rule to retrieve. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Rule data or an error if retrieval failed. + */ +export const getRuleById = async ( + token: string, + Id: number, + phase: 'request' | 'response', + ruleId: number, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/rules_engine/${phase}/rules/${ruleId}`, { + method: 'GET', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error getting rule by ID:', error); + throw error; + } +}; + +/** + * Creates a new rule for a specific phase of an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {'request' | 'response'} phase - The phase for the new rule. + * @param {ApiCreateRulePayload} ruleData - Data of the rule to be created. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The created rule or an error if creation failed. + */ +export const createRule = async ( + token: string, + Id: number, + phase: 'request' | 'response', + ruleData: ApiCreateRulePayload, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/rules_engine/${phase}/rules`, { + method: 'POST', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(ruleData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error creating rule:', error); + throw error; + } +}; +/** + * Updates an existing rule for a specific phase of an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {'request' | 'response'} phase - The phase of the rule to update. + * @param {number} ruleId - ID of the rule to update. + * @param {ApiUpdateRulePayload} ruleData - New data for the rule. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} The updated rule or an error if update failed. + */ +export const updateRule = async ( + token: string, + Id: number, + phase: 'request' | 'response', + ruleId: number, + ruleData: ApiUpdateRulePayload, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/rules_engine/${phase}/rules/${ruleId}`, { + method: 'PATCH', + headers: { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `Token ${token}`, + }, + body: JSON.stringify(ruleData), + }); + const data = await response.json(); + if (debug) console.log('Response:', data); + return data; + } catch (error) { + if (debug) console.error('Error updating rule:', error); + throw error; + } +}; +/** + * Deletes a specific rule from an edge application. + * + * @param {string} token - Authentication token for Azion API. + * @param {number} Id - ID of the edge application. + * @param {'request' | 'response'} phase - The phase of the rule to delete. + * @param {number} ruleId - ID of the rule to delete. + * @param {boolean} [debug] - Enable debug mode for detailed logging. + * @returns {Promise} Resolves when the rule is successfully deleted. + */ +export const deleteRule = async ( + token: string, + Id: number, + phase: 'request' | 'response', + ruleId: number, + debug?: boolean, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${Id}/rules_engine/${phase}/rules/${ruleId}`, { + method: 'DELETE', + headers: { Accept: 'application/json; version=3', Authorization: `Token ${token}` }, + }); + if (debug) console.log('Response status:', response.status); + } catch (error) { + if (debug) console.error('Error deleting rule:', error); + throw error; + } +}; diff --git a/packages/applications/src/rules-engine/services/types.ts b/packages/applications/src/rules-engine/services/types.ts new file mode 100644 index 0000000..99ce5d5 --- /dev/null +++ b/packages/applications/src/rules-engine/services/types.ts @@ -0,0 +1,136 @@ +export interface ApiListRulesParams { + page?: number; + page_size?: number; + sort?: string; + order?: 'asc' | 'desc'; + filter?: string; +} + +export interface ApiListRulesResponse { + count: number; + total_pages: number; + schema_version: number; + links: { + previous: string | null; + next: string | null; + }; + results: Rule[]; +} + +export interface Rule { + id: number; + name: string; + phase: 'request' | 'response'; + behaviors: Behavior[]; + criteria: Criterion[][]; + is_active: boolean; + order: number; + description?: string; +} + +export interface Behavior { + name: string; + target?: string | null | BehaviorTarget; +} + +export interface BehaviorTarget { + captured_array: string; + subject: string; + regex: string; +} + +export interface Criterion { + variable: string; + operator: string; + conditional: 'if' | 'and' | 'or'; + input_value: string; +} + +export interface ApiCreateRulePayload { + name: string; + phase: 'request' | 'response'; + behaviors: Behavior[]; + criteria: Criterion[][]; + is_active?: boolean; + order?: number; + description?: string; +} + +export interface ApiUpdateRulePayload extends Partial {} + +export interface ApiRuleResponse { + results: Rule; + schema_version: number; +} +export interface ApiListRulesParams { + page?: number; + page_size?: number; + sort?: string; + order?: 'asc' | 'desc'; + filter?: string; +} + +export interface ApiListRulesResponse { + count: number; + total_pages: number; + schema_version: number; + links: { + previous: string | null; + next: string | null; + }; + results: Rule[]; +} + +export interface Rule { + id: number; + name: string; + phase: 'request' | 'response'; + behaviors: Behavior[]; + criteria: Criterion[][]; + is_active: boolean; + order: number; + description?: string; +} + +export interface Behavior { + name: string; + target?: string | null | BehaviorTarget; +} + +export interface BehaviorTarget { + captured_array: string; + subject: string; + regex: string; +} + +export interface Criterion { + variable: string; + operator: string; + conditional: 'if' | 'and' | 'or'; + input_value: string; +} + +export interface ApiCreateRulePayload { + name: string; + phase: 'request' | 'response'; + behaviors: Behavior[]; + criteria: Criterion[][]; + is_active?: boolean; + order?: number; + description?: string; +} + +export interface ApiUpdateRulePayload extends Partial {} + +export interface ApiRuleResponse { + results: Rule; + schema_version: number; +} + +export interface ApiListRulesParams { + page?: number; + page_size?: number; + sort?: string; + order?: 'asc' | 'desc'; + filter?: string; +} diff --git a/packages/applications/src/rules-engine/types.ts b/packages/applications/src/rules-engine/types.ts new file mode 100644 index 0000000..df11413 --- /dev/null +++ b/packages/applications/src/rules-engine/types.ts @@ -0,0 +1,5 @@ +import { ApiRuleResponse } from './services/types'; + +export type AzionRule = ApiRuleResponse['results']; + +export { ApiCreateRulePayload, ApiListRulesParams, ApiUpdateRulePayload } from './services/types'; diff --git a/packages/applications/src/types.ts b/packages/applications/src/types.ts new file mode 100644 index 0000000..f080193 --- /dev/null +++ b/packages/applications/src/types.ts @@ -0,0 +1,1181 @@ +import { + ApiCreateCacheSettingPayload, + ApiListCacheSettingsParams, + ApiUpdateCacheSettingPayload, +} from './cache-settings/services/types'; +import { AzionCacheSetting } from './cache-settings/types'; +import { + ApiCreateDeviceGroupPayload, + ApiListDeviceGroupsParams, + ApiUpdateDeviceGroupPayload, +} from './device-groups/services/types'; +import { AzionDeviceGroup } from './device-groups/types'; +import { + ApiCreateFunctionInstancePayload, + ApiListFunctionInstancesParams, + ApiUpdateFunctionInstancePayload, +} from './functions-instances/services/types'; +import { AzionFunctionInstance } from './functions-instances/types'; +import { + ApiApplication, + ApiCreateApplicationPayload, + ApiUpdateApplicationPayload, +} from './main-settings/services/types'; +import { ApiCreateOriginPayload, ApiListOriginsParams, ApiUpdateOriginRequest } from './origins/services/types'; +import { AzionOrigin } from './origins/types'; +import { ApiCreateRulePayload, ApiListRulesParams, ApiUpdateRulePayload } from './rules-engine/services/types'; +import { AzionRule } from './rules-engine/types'; + +/** + * @fileoverview This module defines the types and interfaces used throughout the Azion Application SDK. + * It includes definitions for client options, response structures, and operation interfaces for various + * Azion Edge Application features such as cache settings, origins, rules, device groups, and functions. + * @module application/types + */ + +/** + * Options for configuring the Azion client behavior. + * + * @interface AzionClientOptions + * @property {boolean} [debug] - Enable debug mode for detailed logging. + * @property {boolean} [force] - Force the operation even if it might be destructive. + */ +export interface AzionClientOptions { + debug?: boolean; + force?: boolean; +} + +/** + * Options for retrieving a collection of Azion applications. + * + * @interface AzionApplicationCollectionOptions + * @property {number} [page] - The page number for pagination. + * @property {number} [page_size] - The number of items per page. + * @property {'name' | 'id'} [sort] - The field to sort the results by. + * @property {string} [order_by] - The order of the sorting (e.g., 'asc' or 'desc'). + */ +export interface AzionApplicationCollectionOptions { + page?: number; + page_size?: number; + sort?: 'name' | 'id'; + order_by?: string; +} + +/** + * Generic response structure for Azion API calls. + * + * @interface AzionApplicationResponse + * @template T - The type of data returned in the response. + * @property {T} [data] - The response data. + * @property {{ message: string; operation: string }} [error] - Error information if the call failed. + */ +export interface AzionApplicationResponse { + data?: T; + error?: { + message: string; + operation: string; + }; +} + +/** + * Generic response structure for Azion API calls returning collections. + * + * @interface AzionApplicationCollectionResponse + * @template T - The type of items in the collection. + * @property {{ count: number; total_pages: number; schema_version: number; links: { previous: string | null; next: string | null }; results: T[] }} [data] - The collection data. + * @property {{ message: string; operation: string }} [error] - Error information if the call failed. + */ +export interface AzionApplicationCollectionResponse { + data?: { + count: number; + total_pages: number; + schema_version: number; + links: { + previous: string | null; + next: string | null; + }; + results: T[]; + }; + error?: { + message: string; + operation: string; + }; +} + +/** + * Interface for cache setting operations. + * + * @interface CacheOperations + */ +export interface CacheOperations { + /** + * Creates a new cache setting. + * + * @function + * @name CacheOperations.createCacheSetting + * @param {Object} params - The parameters for creating a cache setting. + * @param {ApiCreateCacheSettingPayload} params.data - The data for the new cache setting. + * @returns {Promise>} A promise that resolves with the created cache setting data or an error. + * + * @example + * const { error, data } = await cacheOperations.createCacheSetting({ + * data: { + * name: 'My Cache Setting', + * browser_cache_settings: 'override', + * cdn_cache_settings: 'override', + * cache_by_query_string: 'ignore', + * cookie_names: ['session_id'], + * enable_stale_cache: true + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created cache setting:', data); + * } + */ + createCacheSetting: (params: { + data: ApiCreateCacheSettingPayload; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Retrieves a specific cache setting. + * + * @function + * @name CacheOperations.getCacheSetting + * @param {Object} params - The parameters for retrieving a cache setting. + * @param {number} params.cacheSettingId - The ID of the cache setting to retrieve. + + * @returns {Promise>} A promise that resolves with the cache setting data or an error. + * + * @example + * const { error, data } = await cacheOperations.getCacheSetting({ + * cacheSettingId: 123, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved cache setting:', data); + * } + */ + getCacheSetting: (params: { + cacheSettingId: number; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Retrieves a list of cache settings. + * + * @function + * @name CacheOperations.getCacheSettings + * @param {Object} params - The parameters for retrieving cache settings. + * @param {ApiListCacheSettingsParams} [params.params] - Optional parameters for filtering and pagination. + + * @returns {Promise>} A promise that resolves with a collection of cache settings or an error. + * + * @example + * const { error, data } = await cacheOperations.getCacheSettings({ + * params: { page: 1, page_size: 20 }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved cache settings:', data.results); + * } + */ + getCacheSettings: (params: { + params?: ApiListCacheSettingsParams; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Updates an existing cache setting. + * + * @function + * @name CacheOperations.updateCacheSetting + * @param {Object} params - The parameters for updating a cache setting. + * @param {number} params.cacheSettingId - The ID of the cache setting to update. + * @param {ApiUpdateCacheSettingPayload} params.data - The updated data for the cache setting. + + * @returns {Promise>} A promise that resolves with the updated cache setting data or an error. + * + * @example + * const { error, data } = await cacheOperations.updateCacheSetting({ + * cacheSettingId: 123, + * data: { + * name: 'Updated Cache Setting', + * browser_cache_settings: 'honor', + * cdn_cache_settings: 'honor' + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Updated cache setting:', data); + * } + */ + updateCacheSetting: (params: { + cacheSettingId: number; + data: ApiUpdateCacheSettingPayload; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Deletes a cache setting. + * + * @function + * @name CacheOperations.deleteCacheSetting + * @param {Object} params - The parameters for deleting a cache setting. + * @param {number} params.cacheSettingId - The ID of the cache setting to delete. + + * @returns {Promise>} A promise that resolves when the cache setting is deleted or rejects with an error. + * + * @example + * const { error, data } = await cacheOperations.deleteCacheSetting({ + * cacheSettingId: 123, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Cache setting deleted successfully'); + * } + */ + deleteCacheSetting: (params: { + cacheSettingId: number; + options?: AzionClientOptions; + }) => Promise>; +} + +/** + * Interface for origin operations. + * + * @interface OriginOperations + */ +export interface OriginOperations { + /** + * Creates a new origin. + * + * @function + * @name OriginOperations.createOrigin + * @param {Object} params - The parameters for creating an origin. + * @param {ApiCreateOriginPayload} params.data - The data for the new origin. + + * @returns {Promise>} A promise that resolves with the created origin data or an error. + * + * @example + * const { error, data } = await originOperations.createOrigin({ + * data: { + * name: 'My Origin', + * origin_type: 'single_origin', + * addresses: [{ address: 'example.com' }], + * host_header: 'example.com' + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created origin:', data); + * } + */ + createOrigin: (params: { + data: ApiCreateOriginPayload; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Retrieves a specific origin. + * + * @function + * @name OriginOperations.getOrigin + * @param {Object} params - The parameters for retrieving an origin. + * @param {string} params.originKey - The key of the origin to retrieve. + + * @returns {Promise>} A promise that resolves with the origin data or an error. + * + * @example + * const { error, data } = await originOperations.getOrigin({ + * originKey: 'abc123', + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved origin:', data); + * } + */ + getOrigin: (params: { + originKey: string; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Retrieves a list of origins. + * + * @function + * @name OriginOperations.getOrigins + * @param {Object} params - The parameters for retrieving origins. + * @param {ApiListOriginsParams} [params.params] - Optional parameters for filtering and pagination. + + * @returns {Promise>} A promise that resolves with a collection of origins or an error. + * + * @example + * const { error, data } = await originOperations.getOrigins({ + * params: { page: 1, page_size: 20 }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved origins:', data.results); + * } + */ + getOrigins: (params: { + params?: ApiListOriginsParams; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Updates an existing origin. + * + * @function + * @name OriginOperations.updateOrigin + * @param {Object} params - The parameters for updating an origin. + * @param {string} params.originKey - The key of the origin to update. + * @param {ApiUpdateOriginRequest} params.data - The updated data for the origin. + + * @returns {Promise>} A promise that resolves with the updated origin data or an error. + * + * @example + * const { error, data } = await originOperations.updateOrigin({ + * originKey: 'abc123', + * data: { + * name: 'Updated Origin', + * host_header: 'updated-example.com' + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Updated origin:', data); + * } + */ + updateOrigin: (params: { + originKey: string; + data: ApiUpdateOriginRequest; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Deletes an origin. + * + * @function + * @name OriginOperations.deleteOrigin + * @param {Object} params - The parameters for deleting an origin. + * @param {string} params.originKey - The key of the origin to delete. + + * @returns {Promise>} A promise that resolves when the origin is deleted or rejects with an error. + * + * @example + * const { error, data } = await originOperations.deleteOrigin({ + * originKey: 'abc123', + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Origin deleted successfully'); + * } + */ + deleteOrigin: (params: { + originKey: string; + options?: AzionClientOptions; + }) => Promise>; +} + +/** + * Interface for rule operations. + * + * @interface RuleOperations + */ +export interface RuleOperations { + /** + * Creates a new rule. + * + * @function + * @name RuleOperations.createRule + * @param {Object} params - The parameters for creating a rule. + * @param {ApiCreateRulePayload} params.data - The data for the new rule. + + * @returns {Promise>} A promise that resolves with the created rule data or an error. + * + * @example + * const { error, data } = await ruleOperations.createRule({ + * data: { + * name: 'My Rule', + * phase: 'request', + * behaviors: [{ name: 'set_origin', target: 'origin1' }], + * criteria: [[{ conditional: 'if', input: '${uri}', operator: 'starts_with', value: '/api' }]] + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created rule:', data); + * } + */ + createRule: (params: { + data: ApiCreateRulePayload; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Retrieves a specific rule. + * + * @function + * @name RuleOperations.getRule + * @param {Object} params - The parameters for retrieving a rule. + * @param {number} params.ruleId - The ID of the rule to retrieve. + + * @returns {Promise>} A promise that resolves with the rule data or an error. + * + * @example + * const { error, data } = await ruleOperations.getRule({ + * ruleId: 123, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved rule:', data); + * } + */ + getRule: (params: { ruleId: number; options?: AzionClientOptions }) => Promise>; + + /** + * Retrieves a list of rules. + * + * @function + * @name RuleOperations.getRules + * @param {Object} params - The parameters for retrieving rules. + * @param {ApiListRulesParams} [params.params] - Optional parameters for filtering and pagination. + + * @returns {Promise>} A promise that resolves with a collection of rules or an error. + * + * @example + * const { error, data } = await ruleOperations.getRules({ + * params: { page: 1, page_size: 20 }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved rules:', data.results); + * } + */ + getRules: (params: { + params?: ApiListRulesParams; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Updates an existing rule. + * + * @function + * @name RuleOperations.updateRule + * @param {Object} params - The parameters for updating a rule. + * @param {number} params.ruleId - The ID of the rule to update. + * @param {ApiUpdateRulePayload} params.data - The updated data for the rule. + + * @returns {Promise>} A promise that resolves with the updated rule data or an error. + * + * @example + * const { error, data } = await ruleOperations.updateRule({ + * ruleId: 123, + * data: { + * name: 'Updated Rule', + * behaviors: [{ name: 'set_origin', target: 'origin2' }] + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Updated rule:', data); + * } + */ + updateRule: (params: { + ruleId: number; + data: ApiUpdateRulePayload; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Deletes a rule. + * + * @function + * @name RuleOperations.deleteRule + * @param {Object} params - The parameters for deleting a rule. + * @param {number} params.ruleId - The ID of the rule to delete. + + * @returns {Promise>} A promise that resolves when the rule is deleted or rejects with an error. + * + * @example + * const { error, data } = await ruleOperations.deleteRule({ + * ruleId: 123, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Rule deleted successfully'); + * } + */ + deleteRule: (params: { ruleId: number; options?: AzionClientOptions }) => Promise>; +} + +/** + * Interface for device group operations. + * + * @interface DeviceGroupOperations + */ +export interface DeviceGroupOperations { + /** + * Creates a new device group. + * + * @function + * @name DeviceGroupOperations.createDeviceGroup + * @param {Object} params - The parameters for creating a device group. + * @param {ApiCreateDeviceGroupPayload} params.data - The data for the new device group. + + * @returns {Promise>} A promise that resolves with the created device group data or an error. + * + * @example + * const { error, data } = await deviceGroupOperations.createDeviceGroup({ + * data: { + * name: 'Mobile Devices', + * user_agent: 'Mobile|Android|iPhone|iPad|iPod' + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created device group:', data); + * } + */ + createDeviceGroup: (params: { + data: ApiCreateDeviceGroupPayload; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Retrieves a specific device group. + * + * @function + * @name DeviceGroupOperations.getDeviceGroup + * @param {Object} params - The parameters for retrieving a device group. + * @param {number} params.deviceGroupId - The ID of the device group to retrieve. + + * @returns {Promise>} A promise that resolves with the device group data or an error. + * + * @example + * const { error, data } = await deviceGroupOperations.getDeviceGroup({ + * deviceGroupId: 123, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved device group:', data); + * } + */ + getDeviceGroup: (params: { + deviceGroupId: number; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Retrieves a list of device groups. + * + * @function + * @name DeviceGroupOperations.getDeviceGroups + * @param {Object} params - The parameters for retrieving device groups. + * @param {ApiListDeviceGroupsParams} [params.params] - Optional parameters for filtering and pagination. + + * @returns {Promise>} A promise that resolves with a collection of device groups or an error. + * + * @example + * const { error, data } = await deviceGroupOperations.getDeviceGroups({ + * params: { page: 1, page_size: 20 }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved device groups:', data.results); + * } + */ + getDeviceGroups: (params: { + params?: ApiListDeviceGroupsParams; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Updates an existing device group. + * + * @function + * @name DeviceGroupOperations.updateDeviceGroup + * @param {Object} params - The parameters for updating a device group. + * @param {number} params.deviceGroupId - The ID of the device group to update. + * @param {ApiUpdateDeviceGroupPayload} params.data - The updated data for the device group. + + * @returns {Promise>} A promise that resolves with the updated device group data or an error. + * + * @example + * const { error, data } = await deviceGroupOperations.updateDeviceGroup({ + * deviceGroupId: 123, + * data: { + * name: 'Updated Mobile Devices', + * user_agent: 'Mobile|Android|iPhone|iPad|iPod|BlackBerry' + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Updated device group:', data); + * } + */ + updateDeviceGroup: (params: { + deviceGroupId: number; + data: ApiUpdateDeviceGroupPayload; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Deletes a device group. + * + * @function + * @name DeviceGroupOperations.deleteDeviceGroup + * @param {Object} params - The parameters for deleting a device group. + * @param {number} params.deviceGroupId - The ID of the device group to delete. + + * @returns {Promise>} A promise that resolves when the device group is deleted or rejects with an error. + * + * @example + * const { error, data } = await deviceGroupOperations.deleteDeviceGroup({ + * deviceGroupId: 123, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Device group deleted successfully'); + * } + */ + deleteDeviceGroup: (params: { + deviceGroupId: number; + options?: AzionClientOptions; + }) => Promise>; +} + +/** + * Interface for function operations. + * + * @interface FunctionOperations + */ +export interface FunctionOperations { + /** + * Creates a new function instance. + * + * @function + * @name FunctionOperations.createFunctionInstance + * @param {Object} params - The parameters for creating a function instance. + * @param {ApiCreateFunctionInstancePayload} params.data - The data for the new function instance. + + * @returns {Promise>} A promise that resolves with the created function instance data or an error. + * + * @example + * const { error, data } = await functionOperations.createFunctionInstance({ + * data: { + * name: 'My Function', + * code: 'async function handleRequest(request) { return new Response("Hello, World!"); }', + * language: 'javascript' + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created function instance:', data); + * } + */ + createFunctionInstance: (params: { + data: ApiCreateFunctionInstancePayload; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Retrieves a specific function instance. + * + * @function + * @name FunctionOperations.getFunctionInstance + * @param {Object} params - The parameters for retrieving a function instance. + * @param {number} params.functionInstanceId - The ID of the function instance to retrieve. + + * @returns {Promise>} A promise that resolves with the function instance data or an error. + * + * @example + * const { error, data } = await functionOperations.getFunctionInstance({ + * functionInstanceId: 123, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved function instance:', data); + * } + */ + getFunctionInstance: (params: { + functionInstanceId: number; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Retrieves a list of function instances. + * + * @function + * @name FunctionOperations.getFunctionInstances + * @param {Object} params - The parameters for retrieving function instances. + * @param {ApiListFunctionInstancesParams} [params.params] - Optional parameters for filtering and pagination. + + * @returns {Promise>} A promise that resolves with a collection of function instances or an error. + * + * @example + * const { error, data } = await functionOperations.getFunctionInstances({ + * params: { page: 1, page_size: 20 }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved function instances:', data.results); + * } + */ + getFunctionInstances: (params: { + params?: ApiListFunctionInstancesParams; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Updates an existing function instance. + * + * @function + * @name FunctionOperations.updateFunctionInstance + * @param {Object} params - The parameters for updating a function instance. + * @param {number} params.functionInstanceId - The ID of the function instance to update. + * @param {ApiUpdateFunctionInstancePayload} params.data - The updated data for the function instance. + + * @returns {Promise>} A promise that resolves with the updated function instance data or an error. + * + * @example + * const { error, data } = await functionOperations.updateFunctionInstance({ + * functionInstanceId: 123, + * data: { + * name: 'Updated Function', + * code: 'async function handleRequest(request) { return new Response("Hello, Updated World!"); }' + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Updated function instance:', data); + * } + */ + updateFunctionInstance: (params: { + functionInstanceId: number; + data: ApiUpdateFunctionInstancePayload; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Deletes a function instance. + * + * @function + * @name FunctionOperations.deleteFunctionInstance + * @param {Object} params - The parameters for deleting a function instance. + * @param {number} params.functionInstanceId - The ID of the function instance to delete. + + * @returns {Promise>} A promise that resolves when the function instance is deleted or rejects with an error. + * + * @example + * const { error, data } = await functionOperations.deleteFunctionInstance({ + * functionInstanceId: 123, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Function instance deleted successfully'); + * } + */ + deleteFunctionInstance: (params: { + functionInstanceId: number; + options?: AzionClientOptions; + }) => Promise>; +} + +/** + * Interface representing an Azion Application with all its associated operations. + * + * @interface AzionApplication + * @extends ApiApplication + */ +export interface AzionApplication extends ApiApplication { + /** + * Operations related to cache settings for the application. + * @type {CacheOperations} + * + * @example + * // Creating a new cache setting + * const { error, data } = await application.cache.createCacheSetting({ + * data: { + * name: 'My Cache Setting', + * browser_cache_settings: 'override', + * cdn_cache_settings: 'override', + * cache_by_query_string: 'ignore' + * } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created cache setting:', data); + * } + */ + cache: CacheOperations; + + /** + * Operations related to origins for the application. + * @type {OriginOperations} + * + * @example + * // Creating a new origin + * const { error, data } = await application.origins.createOrigin({ + * data: { + * name: 'My Origin', + * addresses: [{ address: 'example.com' }], + * origin_type: 'single_origin', + * host_header: 'example.com' + * } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created origin:', data); + * } + */ + origins: OriginOperations; + + /** + * Operations related to rules for the application. + * @type {{ request: RuleOperations; response: RuleOperations }} + */ + rules: { + /** + * Operations for request rules. + * @type {RuleOperations} + * + * @example + * // Creating a new request rule + * const { error, data } = await application.rules.request.createRule({ + * data: { + * name: 'My Request Rule', + * phase: 'request', + * behaviors: [{ name: 'set_origin', target: 'origin1' }], + * criteria: [[{ conditional: 'if', input: '${uri}', operator: 'starts_with', value: '/api' }]] + * } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created request rule:', data); + * } + */ + request: RuleOperations; + + /** + * Operations for response rules. + * @type {RuleOperations} + * + * @example + * // Creating a new response rule + * const { error, data } = await application.rules.response.createRule({ + * data: { + * name: 'My Response Rule', + * phase: 'response', + * behaviors: [{ name: 'add_response_header', target: 'X-Custom-Header', value: 'CustomValue' }], + * criteria: [[{ conditional: 'if', input: '${status}', operator: 'is_equal', value: '200' }]] + * } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created response rule:', data); + * } + */ + response: RuleOperations; + }; + + /** + * Operations related to device groups for the application. + * @type {DeviceGroupOperations} + * + * @example + * // Creating a new device group + * const { error, data } = await application.devices.createDeviceGroup({ + * data: { + * name: 'Mobile Devices', + * user_agent: 'Mobile|Android|iPhone|iPad|iPod' + * } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created device group:', data); + * } + */ + devices: DeviceGroupOperations; + + /** + * Operations related to functions for the application. + * @type {FunctionOperations} + * + * @example + * // Creating a new function instance + * const { error, data } = await application.functions.createFunctionInstance({ + * data: { + * name: 'My Function', + * edge_function_id: 5678, + * args: {} + * } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created function instance:', data); + * } + */ + functions: FunctionOperations; +} + +/** + * Interface for the Azion Application Client, providing methods to interact with Azion Edge Applications. + * + * @interface AzionApplicationClient + */ +export interface AzionApplicationClient { + /** + * Creates a new Azion Edge Application. + * + * @function + * @name AzionApplicationClient.createApplication + * @param {Object} params - The parameters for creating an application. + * @param {ApiCreateApplicationPayload} params.data - The data for the new application. + + * @returns {Promise>} A promise that resolves with the created application data or an error. + * + * @example + * const { error, data } = await applicationClient.createApplication({ + * data: { + * name: 'My New Application', + * delivery_protocol: 'http', + * origin_type: 'single_origin', + * address: 'example.com' + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Created application:', data); + * } + */ + createApplication: (params: { + data: ApiCreateApplicationPayload; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Retrieves a specific Azion Edge Application. + * + * @function + * @name AzionApplicationClient.getApplication + * @param {Object} params - The parameters for retrieving an application. + * @param {number} params.applicationId - The ID of the application to retrieve. + + * @returns {Promise>} A promise that resolves with the application data or an error. + * + * @example + * const { error, data } = await applicationClient.getApplication({ + * applicationId: 123, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved application:', data); + * } + */ + getApplication: (params: { + applicationId: number; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Retrieves a list of Azion Edge Applications. + * + * @function + * @name AzionApplicationClient.getApplications + * @param {Object} params - The parameters for retrieving applications. + * @param {AzionApplicationCollectionOptions} [params.params] - Optional parameters for filtering and pagination. + + * @returns {Promise>} A promise that resolves with a collection of applications or an error. + * + * @example + * const { error, data } = await applicationClient.getApplications({ + * params: { page: 1, page_size: 20 }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Retrieved applications:', data.results); + * } + */ + getApplications: (params: { + params?: AzionApplicationCollectionOptions; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Updates an existing Azion Edge Application. + * + * @function + * @name AzionApplicationClient.putApplication + * @param {Object} params - The parameters for updating an application. + * @param {number} params.applicationId - The ID of the application to update. + * @param {ApiUpdateApplicationPayload} params.data - The updated data for the application. + + * @returns {Promise>} A promise that resolves with the updated application data or an error. + * + * @example + * const { error, data } = await applicationClient.putApplication({ + * applicationId: 123, + * data: { + * name: 'Updated Application', + * delivery_protocol: 'https' + * }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Updated application:', data); + * } + */ + putApplication: (params: { + applicationId: number; + data: ApiUpdateApplicationPayload; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Deletes an Azion Edge Application. + * + * @function + * @name AzionApplicationClient.deleteApplication + * @param {Object} params - The parameters for deleting an application. + * @param {number} params.applicationId - The ID of the application to delete. + + * @returns {Promise>} A promise that resolves when the application is deleted or rejects with an error. + * + * @example + * const { error, data } = await applicationClient.deleteApplication({ + * applicationId: 123, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Application deleted successfully'); + * } + */ + deleteApplication: (params: { + applicationId: number; + options?: AzionClientOptions; + }) => Promise>; + + /** + * Partially updates an existing Azion Edge Application. + * + * @function + * @name AzionApplicationClient.patchApplication + * @param {Object} params - The parameters for partially updating an application. + * @param {number} params.applicationId - The ID of the application to update. + * @param {Partial} params.data - The partial data for updating the application. + + * @returns {Promise>} A promise that resolves with the updated application data or an error. + * + * @example + * const { error, data } = await applicationClient.patchApplication({ + * applicationId: 123, + * data: { name: 'Partially Updated Application' }, + * options: { debug: true } + * }); + * if (error) { + * console.error('Error:', error); + * } else { + * console.log('Partially updated application:', data); + * } + */ + patchApplication: (params: { + applicationId: number; + data: Partial; + options?: AzionClientOptions; + }) => Promise>; +} + +/** + * Function type for creating an Azion Application Client. + * + * @param {Object} [config] - Configuration options for the application client. + * @param {string} [config.token] - Authentication token for Azion API. If not provided, + * the client will attempt to use the AZION_TOKEN environment variable. + * @param {AzionClientOptions} [config.options] - Additional client options. + * + * @returns {AzionApplicationClient} An instance of the Azion Application Client. + * + * @example + * // Create an application client with a token and debug mode enabled + * const appClient = createAzionApplicationClient({ + * token: 'your-api-token', + * options: { debug: true } + * }); + * + * @example + * // Create an application client using environment variables for token + * const appClient = createAzionApplicationClient(); + */ +export type CreateAzionApplicationClient = ( + config?: Partial<{ token: string; options?: AzionClientOptions }>, +) => AzionApplicationClient; diff --git a/packages/applications/src/utils.ts b/packages/applications/src/utils.ts new file mode 100644 index 0000000..3eb7420 --- /dev/null +++ b/packages/applications/src/utils.ts @@ -0,0 +1,56 @@ +const envDebugFlag = process.env.AZION_DEBUG && process.env.AZION_DEBUG === 'true'; + +export const resolveToken = (token?: string) => token ?? process.env.AZION_TOKEN ?? ''; +export const resolveDebug = (debug?: boolean) => debug ?? !!envDebugFlag; + +/** + * Maps API error response to a standardized error object. + * @param error - The full API error response object or string. + * @param operation - The name of the operation that failed. + * @param defaultMessage - The default error message to use if no specific message is found. + * @returns A standardized error object with operation and message. + */ +export const mapApiError = ( + error: unknown, + operation: string, + defaultMessage: string, +): { message: string; operation: string } => { + let errorMessage: string | undefined; + + if (error && typeof error === 'object') { + // Caso 1: { name_already_in_use: 'message' } + const errorKey = Object.keys(error)[0]; + if (errorKey && typeof error[errorKey as keyof typeof error] === 'string') { + errorMessage = error[errorKey as keyof typeof error] as string; + } + // Case 2: { detail: 'message' } + else if ('detail' in error && typeof error.detail === 'string') { + errorMessage = error.detail; + } + // Case 2: { message: 'message' } + else if ('message' in error && typeof error.message === 'string') { + errorMessage = error.message; + } + // Case 4: { error: 'message' } or { error: { message: 'message' } } + else if ('error' in error) { + if (typeof error.error === 'string') { + errorMessage = error.error; + } else if (typeof error.error === 'object' && error.error !== null) { + const innerErrorKey = Object.keys(error.error)[0]; + if (innerErrorKey && typeof (error.error as Record)[innerErrorKey] === 'string') { + errorMessage = (error.error as Record)[innerErrorKey]; + } else if ('message' in error.error && typeof (error.error as { message: unknown }).message === 'string') { + errorMessage = (error.error as { message: string }).message; + } + } + } + } else if (typeof error === 'string') { + errorMessage = error; + } + + // Use default Message as final fallback if no error message is found + return { + message: errorMessage || defaultMessage, + operation, + }; +}; diff --git a/packages/applications/tsconfig.json b/packages/applications/tsconfig.json new file mode 100644 index 0000000..500c28a --- /dev/null +++ b/packages/applications/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "ESNext", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "paths": { + "azion": ["../client/src/index.ts"], + "azion/sql": ["../sql/src/index.ts"], + "azion/storage": ["../storage/src/index.ts"], + "azion/purge": ["../purge/src/index.ts"], + "azion/config": ["../config/src/index.ts"] + } + } +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index a3382e7..6669215 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,3 +1,4 @@ +import createAzionApplicationClient, { AzionApplicationClient } from 'azion/applications'; import { defineConfig } from 'azion/config'; import createPurgeClient, { AzionPurgeClient } from 'azion/purge'; import createSqlClient, { AzionSQLClient } from 'azion/sql'; @@ -11,7 +12,7 @@ import { AzionClient, AzionClientConfig } from './types'; * @param {AzionClientConfig} [config] - Client configuration options. * @param {string} [config.token] - Authentication token for Azion API. * @param {boolean} [config.debug=false] - Enable debug mode for detailed logging. - * @returns {AzionClient} An object containing SQL, Storage, and Purge clients. + * @returns {AzionClient} An object containing SQL, Storage, Purge, and Edge Application clients. * * @example * // Create a client with a token and debug mode enabled @@ -41,7 +42,13 @@ function createClient({ token, options }: AzionClientConfig = {}): AzionClient { const purgeClient: AzionPurgeClient = createPurgeClient({ token, options }); /** - * Azion Client object containing Storage, SQL, and Purge clients. + * Edge Application client with methods to interact with Azion Edge Applications. + * @type {AzionApplicationClient} + */ + const applicationClient: AzionApplicationClient = createAzionApplicationClient({ token, options }); + + /** + * Azion Client object containing Storage, SQL, Purge, and Edge Application clients. * Use this object to interact with various Azion services. * * @type {AzionClient} @@ -49,11 +56,13 @@ function createClient({ token, options }: AzionClientConfig = {}): AzionClient { * @property {AzionStorageClient} storage - Client for Azion Edge Storage operations. * @property {AzionSQLClient} sql - Client for Azion Edge SQL database operations. * @property {AzionPurgeClient} purge - Client for Azion Edge Purge operations. + * @property {AzionApplicationClient} application - Client for Azion Edge Application operations. */ const client: AzionClient = { storage: storageClient, sql: sqlClient, purge: purgeClient, + application: applicationClient, }; return client; diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index 276566c..6aa468e 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -1,9 +1,20 @@ /* eslint-disable no-unused-vars */ +import { AzionApplicationClient } from '../../applications/src/types'; import { AzionPurgeClient } from '../../purge/src/types'; import { AzionClientOptions, AzionSQLClient } from '../../sql/src/types'; import { AzionStorageClient } from '../../storage/src/types'; +/** + * Azion Client interface containing all available service clients. + * + * @interface AzionClient + * + * @property {AzionStorageClient} storage - Client for Azion Edge Storage operations. + * @property {AzionSQLClient} sql - Client for Azion Edge SQL database operations. + * @property {AzionPurgeClient} purge - Client for Azion Edge Purge operations. + * @property {AzionApplicationClient} - Client for Azion Edge Application operations. + */ export interface AzionClient { /** * Storage client with methods to interact with Azion Edge Storage. @@ -88,16 +99,76 @@ export interface AzionClient { * @example * // Purge a URL * const purgeResult = await client.purge.purgeURL(['http://example.com/image.jpg']); + */ + purge: AzionPurgeClient; + + /** + * Edge Application client with methods to interact with Azion Edge Applications. + * + * @type {AzionApplicationClient} * * @example - * // Purge a cache key - * const cacheKeyResult = await client.purge.purgeCacheKey(['my-cache-key-1', 'my-cache-key-2']); + * // Create a new Edge Application + * const { data: newApp } = await client.application.createApplication({ + * data: { + * name: 'My New App', + * delivery_protocol: 'http', + * origin_type: 'single_origin', + * address: 'example.com' + * } + * }); * * @example - * // Purge using a wildcard - * const wildcardResult = await client.purge.purgeWildCard(['http://example.com/*']); + * // Get all Edge Applications + * const { data: allApps } = await client.application.getApplications({ + * params: { page: 1, page_size: 20, sort: 'name', order_by: 'asc' } + * }); + * + * @example + * // Get a specific Edge Application and perform operations + * const { data: app } = await client.application.getApplication({ applicationId: 123 }); + * if (app) { + * // Create a new cache setting + * const { data: newCacheSetting } = await app.cache.createCacheSetting({ + * data: { name: 'My Cache Setting', browser_cache_settings: 'override' } + * }); + * + * // Create a new origin + * const { data: newOrigin } = await app.origins.createOrigin({ + * data: { name: 'My Origin', addresses: [{ address: 'api.example.com' }] } + * }); + * + * // Create a new rule + * const { data: newRule } = await app.rules.request.createRule({ + * data: { + * name: 'My Rule', + * behaviors: [{ name: 'set_origin', target: newOrigin.id }], + * criteria: [{ condition: 'starts_with', variable: '${uri}', input: '/api' }] + * } + * }); + * + * // Create a new function instance + * const { data: newFunction } = await app.functions.createFunctionInstance({ + * data: { + * name: 'My Function Instance', + * edge_function_id: 1234, + * args: {} + * } + * }); + * } + * + * @example + * // Update an Edge Application + * const { data: updatedApp } = await client.application.putApplication({ + * applicationId: 123, + * data: { name: 'Updated App Name', delivery_protocol: 'https' } + * }); + * + * @example + * // Delete an Edge Application + * const { data: deletedApp } = await client.application.deleteApplication({ applicationId: 123 }); */ - purge: AzionPurgeClient; + application: AzionApplicationClient; } /** diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 500c28a..3c34679 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -12,7 +12,8 @@ "azion/sql": ["../sql/src/index.ts"], "azion/storage": ["../storage/src/index.ts"], "azion/purge": ["../purge/src/index.ts"], - "azion/config": ["../config/src/index.ts"] + "azion/config": ["../config/src/index.ts"], + "azion/applications": ["../applications/src/index.ts"] } } } diff --git a/packages/config/README.md b/packages/config/README.md index a373ed7..080acbc 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -58,7 +58,7 @@ const config = defineConfig({ name: 'example.com', cnameAccessOnly: false, cnames: ['www.example.com', 'cdn.example.com'], - edgeApplicationId: 12345, + Id: 12345, edgeFirewallId: 67890, digitalCertificateId: null, mtls: { @@ -146,7 +146,7 @@ Type definition for the domain configuration. - `name: string` - The domain name. - `cnameAccessOnly?: boolean` - Whether to restrict access only to CNAMEs. - `cnames?: string[]` - List of CNAMEs for the domain. -- `edgeApplicationId?: number` - ID of the edge application. +- `Id?: number` - ID of the edge application. - `edgeFirewallId?: number` - ID of the edge firewall. - `digitalCertificateId?: string | number | null` - ID of the digital certificate. - `mtls?: MTLSConfig` - Configuration for mTLS. @@ -237,6 +237,7 @@ Type definition for the response rule configuration. - `match: string` - Match criteria for the rule. - `variable?: string` - Variable to be used in the match. - `behavior?: ResponseBehavior` - Behavior to apply when the rule matches. + - `setCookie?: string | null` - Set a cookie. - `setHeaders?: string[]` - Set headers. - `deliver?: boolean | null` - Deliver the content. @@ -253,6 +254,7 @@ Type definition for the response rule configuration. Type definition for the rule set. **Properties:** + - `request: AzionRequestRule[]` - Ruleset for Request phase. - `response?: AzionResponseRule[]` - Ruleset for Response phase. diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index c746eb5..b1ded23 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -9,7 +9,7 @@ import { AzionConfig } from './types'; * @param {string} config.domain.name - The domain name. * @param {boolean} [config.domain.cnameAccessOnly] - Whether to restrict access only to CNAMEs. * @param {string[]} [config.domain.cnames] - List of CNAMEs for the domain. - * @param {number} [config.domain.edgeApplicationId] - ID of the edge application. + * @param {number} [config.domain.Id] - ID of the edge application. * @param {number} [config.domain.edgeFirewallId] - ID of the edge firewall. * @param {string|number|null} [config.domain.digitalCertificateId] - ID of the digital certificate. * @param {Object} [config.domain.mtls] - Configuration for mTLS. @@ -129,7 +129,7 @@ import { AzionConfig } from './types'; * name: 'example.com', * cnameAccessOnly: false, * cnames: ['www.example.com', 'cdn.example.com'], - * edgeApplicationId: 12345, + * Id: 12345, * edgeFirewallId: 67890, * digitalCertificateId: null, * mtls: { diff --git a/packages/config/src/types.ts b/packages/config/src/types.ts index 67c634b..93adbac 100644 --- a/packages/config/src/types.ts +++ b/packages/config/src/types.ts @@ -9,7 +9,7 @@ export type AzionDomain = { /** List of CNAMEs associated with the domain */ cnames?: string[]; /** Associated edge application ID */ - edgeApplicationId?: number; + Id?: number; /** Associated edge firewall ID */ edgeFirewallId?: number; /** Digital certificate ID */ From 0ff8a048d8b1b8315c6b564fee3047530f37f627 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 9 Sep 2024 22:04:51 +0000 Subject: [PATCH 32/45] chore(release): 1.7.0-stage.10 [skip ci] ## [1.7.0-stage.10](https://github.com/aziontech/lib/compare/v1.7.0-stage.9...v1.7.0-stage.10) (2024-09-09) ### Features * add package application (#37) ([e53c5b0](https://github.com/aziontech/lib/commit/e53c5b0908233484d976c2bb25df921bb9588b87)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c40934..f2bddaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.7.0-stage.10](https://github.com/aziontech/lib/compare/v1.7.0-stage.9...v1.7.0-stage.10) (2024-09-09) + + +### Features + +* add package application (#37) ([e53c5b0](https://github.com/aziontech/lib/commit/e53c5b0908233484d976c2bb25df921bb9588b87)) + ## [1.7.0-stage.9](https://github.com/aziontech/lib/compare/v1.7.0-stage.8...v1.7.0-stage.9) (2024-09-05) diff --git a/package.json b/package.json index 28de2eb..f56ba09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.7.0-stage.9", + "version": "1.7.0-stage.10", "description": "Azion Packages for Edge Computing.", "bin": { "azion": "./bin/azion" From 9a020c14907f65009da97dc507fe901017452fe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Filho?= Date: Tue, 10 Sep 2024 10:42:50 -0300 Subject: [PATCH 33/45] feat: add package domains (#38) * feat: add domains package and configuration files * feat: add azion update domain * feat: add deleteDomain method to azion/domains * docs: updating the azion/domains readme * feat: updated the return of azion/domains functions * docs: update readme azion/domains * refactor: listDomains to getDomains * chore: adding azion/domains to the main readme and client package * refactor: update property data to results in AzionDomains type * refactor: update AzionCreateDomain and AzionUpdateDomain types * docs: update jsdocs azion/domains * chore: update import paths for domain services and utils * test: update updateDomain function to handle missing edgeApplicationId --- README.md | 34 ++ package-lock.json | 16 + package.json | 8 + packages/client/README.MD | 61 ++ packages/client/src/index.ts | 9 + packages/client/src/types.ts | 27 + packages/client/tsconfig.json | 1 + packages/domains/.gitignore | 2 + packages/domains/README.md | 407 +++++++++++++ packages/domains/jest.config.ts | 18 + packages/domains/package.json | 37 ++ packages/domains/src/index.test.ts | 657 +++++++++++++++++++++ packages/domains/src/index.ts | 449 ++++++++++++++ packages/domains/src/services/api/index.ts | 226 +++++++ packages/domains/src/services/api/types.ts | 44 ++ packages/domains/src/types.ts | 227 +++++++ packages/domains/src/utils/index.test.ts | 103 ++++ packages/domains/src/utils/index.ts | 33 ++ packages/domains/tsconfig.json | 12 + 19 files changed, 2371 insertions(+) create mode 100644 packages/domains/.gitignore create mode 100644 packages/domains/README.md create mode 100644 packages/domains/jest.config.ts create mode 100644 packages/domains/package.json create mode 100644 packages/domains/src/index.test.ts create mode 100644 packages/domains/src/index.ts create mode 100644 packages/domains/src/services/api/index.ts create mode 100644 packages/domains/src/services/api/types.ts create mode 100644 packages/domains/src/types.ts create mode 100644 packages/domains/src/utils/index.test.ts create mode 100644 packages/domains/src/utils/index.ts create mode 100644 packages/domains/tsconfig.json diff --git a/README.md b/README.md index c7fe0f9..74e42f9 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ These libraries are designed to be versatile and can be used both within and out - [Storage](#storage) - [SQL](#sql) - [Purge](#purge) + - [Domains](#domains) - [Utilities](#utilities) - [Cookies](#cookies) - [Jwt](#jwt) @@ -232,6 +233,39 @@ if (cacheKeyResult) { Read more in the [Purge README](./packages/purge/README.md). +### Domains + +The Domains library provides methods to interact with Azion Edge Domains. + +#### Examples + +**JavaScript:** + +```javascript +import { createClient } from 'azion/domains'; + +const client = createClient({ token: 'your-token' }); + +const { data: allDomains } = await client.getDomains(); + +console.log(`Retrieved ${allDomains.count} domains`); +``` + +**TypeScript:** + +```typescript +import { createClient } from 'azion/domains'; +import type { AzionDomainsClient, AzionDomainsResponse, AzionDomains } from 'azion/domains'; + +const client: AzionDomainsClient = createClient({ token: 'your-token' }); + +const { data: allDomains }: AzionDomainsResponse = await client.getDomains(); + +console.log(`Retrieved ${allDomains.count} domains`); +``` + +Read more in the [Domains README](./packages/domains/README.md). + ## Utilities ### Cookies diff --git a/package-lock.json b/package-lock.json index 1d62703..26def44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2214,6 +2214,10 @@ "resolved": "packages/cookies", "link": true }, + "node_modules/@lib/domains": { + "resolved": "packages/domains", + "link": true + }, "node_modules/@lib/jwt": { "resolved": "packages/jwt", "link": true @@ -14396,6 +14400,18 @@ "ts-node": "^10.9.2" } }, + "packages/domains": { + "name": "@lib/domains", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@jest/globals": "^29.7.0", + "@types/jest": "^29.5.12", + "jest": "^29.7.0", + "ts-jest": "^29.1.5", + "ts-node": "^10.9.2" + } + }, "packages/jwt": { "name": "@lib/jwt", "version": "1.0.0", diff --git a/package.json b/package.json index f56ba09..6bc9532 100644 --- a/package.json +++ b/package.json @@ -135,6 +135,11 @@ "require": "./packages/utils/dist/index.js", "import": "./packages/utils/dist/index.mjs", "types": "./packages/utils/dist/index.d.ts" + }, + "./domains": { + "require": "./packages/domains/dist/index.js", + "import": "./packages/domains/dist/index.mjs", + "types": "./packages/domains/dist/index.d.ts" } }, "typesVersions": { @@ -171,6 +176,9 @@ ], "utils": [ "./packages/utils/dist/index.d.ts" + ], + "domains": [ + "./packages/domains/dist/index.d.ts" ] } }, diff --git a/packages/client/README.MD b/packages/client/README.MD index e056170..1811645 100644 --- a/packages/client/README.MD +++ b/packages/client/README.MD @@ -13,6 +13,7 @@ Azion Client provides a unified interface to interact with various Azion service - [Storage Client](#storage-client) - [SQL Client](#sql-client) - [Purge Client](#purge-client) + - [Domains Client](#domains-client) - [API Reference](#api-reference) - [`createClient`](#createclient) - [Types](#types) @@ -219,6 +220,65 @@ if (wildcardResult) { } ``` +#### Domains Client + +**JavaScript:** + +```javascript +import { createClient } from 'azion'; + +const client = createClient({ token: 'your-api-token', debug: true }); + +const { data: newDomain } = await client.domains.createDomain('example.com', 'my-new-domain'); + +if (newDomain) { + console.log(`Domain created with name: ${newDomain.name}`); +} + +const { data: allDomains } = await client.domains.getDomains(); + +if (allDomains) { + console.log(`Retrieved ${allDomains.count} domains`); +} + +const { data: deletedDomain } = await client.domains.deleteDomain('example.com'); + +if (deletedDomain) { + console.log(`Domain deleted with name: ${deletedDomain.name}`); +} +``` + +**TypeScript:** + +```typescript +import { createClient } from 'azion'; +import { AzionClient, AzionDomainsResponse, AzionDomain, AzionDeletedDomain, AzionDomains } from 'azion/domains'; + +const client: AzionClient = createClient({ token: 'your-api-token', debug: true }); + +const { data: newDomain }: AzionDomainsResponse = await client.domains.createDomain( + 'example.com', + 'my-new-domain', +); + +if (newDomain) { + console.log(`Domain created with name: ${newDomain.name}`); +} + +const { data: allDomains }: AzionDomainsResponse = await client.domains.getDomains(); + +if (allDomains) { + console.log(`Retrieved ${allDomains.count} domains`); +} + +const { data: deletedDomain }: AzionDomainsResponse = + await client.domains.deleteDomain('example.com'); + +if (deletedDomain) { + console.log(`Domain deleted with name: ${deletedDomain.name}`); +} +``` + ## API Reference ### `createClient` @@ -255,6 +315,7 @@ An object with methods to interact with Azion services. - `storage: StorageClient` - `sql: AzionSQLClient` - `purge: PurgeClient` +- `domains: DomainsClient` ## Contributing diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 6669215..04cd015 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,5 +1,6 @@ import createAzionApplicationClient, { AzionApplicationClient } from 'azion/applications'; import { defineConfig } from 'azion/config'; +import createDomainsClient, { AzionDomainsClient } from 'azion/domains'; import createPurgeClient, { AzionPurgeClient } from 'azion/purge'; import createSqlClient, { AzionSQLClient } from 'azion/sql'; import createStorageClient, { AzionStorageClient } from 'azion/storage'; @@ -42,6 +43,13 @@ function createClient({ token, options }: AzionClientConfig = {}): AzionClient { const purgeClient: AzionPurgeClient = createPurgeClient({ token, options }); /** + * Domains client with methods to interact with Azion Edge Domains. + * @type {AzionCreateClientDomains} + */ + const domainsClient: AzionDomainsClient = createDomainsClient({ token, options }); + + /** + * Azion Client object containing Storage, SQL, and Purge clients. * Edge Application client with methods to interact with Azion Edge Applications. * @type {AzionApplicationClient} */ @@ -62,6 +70,7 @@ function createClient({ token, options }: AzionClientConfig = {}): AzionClient { storage: storageClient, sql: sqlClient, purge: purgeClient, + domains: domainsClient, application: applicationClient, }; diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index 6aa468e..49c0aa5 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -1,5 +1,6 @@ /* eslint-disable no-unused-vars */ +import { AzionDomainsClient } from 'azion/domains'; import { AzionApplicationClient } from '../../applications/src/types'; import { AzionPurgeClient } from '../../purge/src/types'; import { AzionClientOptions, AzionSQLClient } from '../../sql/src/types'; @@ -102,6 +103,32 @@ export interface AzionClient { */ purge: AzionPurgeClient; + /* Domains client with methods to interact with Azion Edge Domains. + * + * @type {AzionDomainsClient} + * + * @example + * // Create a new domain + * const { data: newDomain } = await client.domains.createDomain('example.com'); + * + * @example + * // Get all domains + * const { data, error } = await client.domains.getDomains(); + * + * @example + * // Get a specific domain and perform operations + * const { data: domain, error } = await client.domains.getDomain('example.com'); + * if (domain) { + * + * // Update domain + * const updatedDetails = await domain.updateDomain({ name: 'new-origin.example.com' }); + * + * // Delete a domain + * const deletedDomain = await domain.deleteDomain(); + * } + */ + domains: AzionDomainsClient; + /** * Edge Application client with methods to interact with Azion Edge Applications. * diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 3c34679..1407803 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -13,6 +13,7 @@ "azion/storage": ["../storage/src/index.ts"], "azion/purge": ["../purge/src/index.ts"], "azion/config": ["../config/src/index.ts"], + "azion/domains": ["../domains/src/index.ts"], "azion/applications": ["../applications/src/index.ts"] } } diff --git a/packages/domains/.gitignore b/packages/domains/.gitignore new file mode 100644 index 0000000..76add87 --- /dev/null +++ b/packages/domains/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/packages/domains/README.md b/packages/domains/README.md new file mode 100644 index 0000000..e2a0453 --- /dev/null +++ b/packages/domains/README.md @@ -0,0 +1,407 @@ +# Azion Edge Domains + +Azion Edge Domains provides a simple interface to interact with the Azion Edge Domains API, allowing you to create, list, get, update, and delete domains. + +## Table of Contents + +- [Installation](#installation) +- [Environment Variables](#environment-variables) +- [Debug Mode](#debug-mode) +- [Usage](#usage) + - [Using Environment Variables](#using-environment-variables) + - [Direct Method Calls](#direct-method-calls) + - [Client Configuration](#client-configuration) + - [API Examples](#api-examples) + - [Create Domain](#create-domain) + - [List Domains](#list-domains) + - [Get Domain](#get-domain) + - [Update Domain](#update-domain) + - [Delete Domain](#delete-domain) + - [Using Client](#using-client) +- [API Reference](#api-reference) + - [`createDomain`](#createdomain) + - [`getDomains`](#getdomains) + - [`getDomain`](#getdomain) + - [`updateDomain`](#updatedomain) + - [`deleteDomain`](#deletedomain) +- [Types](#types) + - [`ClientConfig`](#clientconfig) + - [`AzionDomainsResponse`](#aziondomainsresponset) + - [`AzionDomain`](#aziondomain) + - [`AzionDomains`](#aziondomains) + - [`AzionDeleteDomain`](#aziondeletedomain) +- [Contributing](#contributing) + +## Installation + +Install the package using npm or yarn: + +```sh +npm install azion +``` + +or + +```sh +yarn add azion +``` + +## Environment Variables + +You can configure the client using the following environment variables: + +- `AZION_TOKEN`: Your Azion API token. +- `AZION_DEBUG`: Enable debug mode (true/false). + +Example `.env` file: + +```sh +AZION_TOKEN=your-api-token +AZION_DEBUG=true +``` + +## Debug Mode + +Debug mode provides detailed logging of the API requests and responses. You can enable debug mode by setting the `AZION_DEBUG` environment variable to `true` or by passing `true` as the `debug` parameter in the methods. + +## Usage + +### Using Environment Variables + +You can use the environment variables to configure the client without passing the token and debug parameters directly. + +### Direct Method Calls + +You can use the provided wrapper methods to interact with the Azion Edge Domains. + +### Client Configuration + +You can create a client instance with specific configurations. + +### API Examples + +#### Create Domain + +**JavaScript:** + +```javascript +import { createDomain } from 'azion/domains'; + +const { data: domain, error } = await createDomain({ name: 'example domain', edgeApplicationId: 123 }); +if (domain) { + console.log(`Domain created with URL: ${domain.url}`); +} else { + console.error('Failed to create domain', error); +} +``` + +**TypeScript:** + +```typescript +import { createDomain } from 'azion/domains'; +import type { AzionDomain, AzionDomainsResponse } from 'azion/domains'; + +const { data: domain, error }: AzionDomainsResponse = await createDomain({ + name: 'example domain', + edgeApplicationId: 123, +}); +if (domain) { + console.log(`Domain created with ID: ${domain.id}`); +} else { + console.error('Failed to create domain', error); +} +``` + +### List Domains + +**JavaScript:** + +```javascript +import { getDomains } from 'azion/domains'; + +const { data: domains, error } = await getDomains(); + +if (domains) { + console.log(`Found ${domains.count} domains`); +} else { + console.error('Failed to list domains', error); +} +``` + +**TypeScript:** + +```typescript +import { getDomains } from 'azion/domains'; +import type { AzionDomains, AzionDomainsResponse } from 'azion/domains'; + +const { data: domains, error }: AzionDomainsResponse = await getDomains(); + +if (domains) { + console.log(`Found ${domains.count} domains`); +} else { + console.error('Failed to list domains', error); +} +``` + +### Get Domain + +**JavaScript:** + +```javascript +import { getDomain } from 'azion/domains'; + +const domainId = 123; +const { data: domain, error } = await getDomain(domainId); + +if (domain) { + console.log(`Found domain with name: ${domain.name}`); +} else { + console.error('Failed to get domain', error); +} +``` + +**TypeScript:** + +```typescript +import { getDomain } from 'azion/domains'; +import type { AzionDomain, AzionDomainsResponse } from 'azion/domains'; + +const domainId = 123; +const { data: domain, error }: AzionDomainsResponse = await getDomain(domainId); + +if (domain) { + console.log(`Found domain with name: ${domain.name}`); +} else { + console.error('Failed to get domain', error); +} +``` + +### Update Domain + +**JavaScript:** + +```javascript +import { updateDomain } from 'azion/domains'; + +const domainId = 123; +const { data: domain, error } = await updateDomain(domainId, { name: 'new domain name', edgeApplicationId: 456 }); + +if (domain) { + console.log(`Updated domain with name: ${domain.name}`); +} else { + console.error('Failed to update domain', error); +} +``` + +**TypeScript:** + +```typescript +import { updateDomain } from 'azion/domains'; +import type { AzionDomain, AzionDomainsResponse } from 'azion/domains'; + +const domainId = 123; +const { data: domain, error }: AzionDomainsResponse = await updateDomain(domainId, { + name: 'new domain name', + edgeApplicationId: 456, +}); + +if (domain) { + console.log(`Updated domain with name: ${domain.name}`); +} else { + console.error('Failed to update domain', error); +} +``` + +### Delete Domain + +**JavaScript:** + +```javascript +import { deleteDomain } from 'azion/domains'; + +const domainId = 123; +const { data: deletedDomain, error } = await deleteDomain(domainId); + +if (deletedDomain) { + console.log(`Deleted domain with ID: ${deletedDomain.id}`); +} else { + console.error('Failed to delete domain', error); +} +``` + +**TypeScript:** + +```typescript +import { deleteDomain } from 'azion/domains'; +import type { AzionDomain, AzionDomainsResponse } from 'azion/domains'; + +const domainId = 123; +const { data: deletedDomain, error }: AzionDomainsResponse = await deleteDomain(domainId); + +if (deletedDomain) { + console.log(`Deleted domain with ID: ${deletedDomain.id}`); +} else { + console.error('Failed to delete domain', error); +} +``` + +### Using Client + +**JavaScript:** + +```javascript +import { createClient } from 'azion/domains'; + +const client = createClient({ token: 'your-api-token', { debug: true } }); + +const { data: newDomain, error } = await client.createDomain({ name: 'example domain', edgeApplicationId: 123 }); +if (newDomain) { + console.log(`Domain created with ID: ${newDomain.id}`); +} + + +``` + +**TypeScript:** + +```typescript +import { createClient } from 'azion/domains'; +import type { AzionDomain, AzionDomainsClient } from 'azion/domains'; + +const client: AzionDomainsClient = createClient({ token: 'your-api-token', { debug: true } }); + +const { data: newDomain, error }: AzionDomainsResponse = await client.createDomain({ name: 'example domain', edgeApplicationId: 123 }); +if (newDomain) { + console.log(`Domain created with ID: ${newDomain.id}`); +} + +``` + +## API Reference + +### `createDomain` + +Creates a new domain. + +**Parameters:** + +- `{ data: domain, error }: AzionCreateDomain` - Domain object. +- `options?: { debug?: boolean }` - Object options params. + +**Returns:** + +- `Promise>` - The created domain object or error failed. + +### `getDomains` + +Lists all domains. + +**Parameters:** + +- `options?: { debug?: boolean }` - Object options params. +- `queryParams?: { pageSize?: number; page?: number; order?: 'id' | 'name'; sort?: 'asc' | 'desc' }` - Query parameters. + +**Returns:** + +- `Promise>` - An array of domain objects or error failed. + +### `getDomain` + +Get a domain by ID. + +**Parameters:** + +- `domainId: number` - Domain ID. +- `options?: { debug?: boolean }` - Object options params. + +**Returns:** + +- `Promise>` - The domain object or state failed. + +### `updateDomain` + +Update a domain. + +**Parameters:** + +- `domainId: number` - Domain ID. +- `{ data: domain, error }: AzionUpdateDomain` - Domain object. +- `options?: { debug?: boolean }` - Object options params. + +**Returns:** + +- `Promise>` - The updated domain object or state failed. + +### `deleteDomain` + +Delete a domain. + +**Parameters:** + +- `domainId: number` - Domain ID. +- `options?: { debug?: boolean }` - Object options params. + +**Returns:** + +- `Promise>` - The deleted domain id or state failed. + +### `createClient` + +Creates a new Azion Domains client. + +**Parameters:** + +- `config?: Partial<{ token: string; options?: OptionsParams }>` - Configuration options for the Domain client. + +**Returns:** + +- `AzionDomainsClient` - An object with methods to interact with Domains. + +## Types + +### `ClientConfig` + +Configuration options for the Azion Domains client. + +- `token?: string` - Your Azion API token. +- `options?: OptionsParams` - Object options params. + - `debug?: boolean` - Enable debug mode for detailed logging. + +### `AzionDomainsResponse` + +- `data?: T` - The response data. +- `error?: { message: string; operation: string;}` - The error object. + +### `AzionDomain` + +- `state: 'pending' | 'executed' | 'failed'` - State of the domain. +- `id?: number` - Domain ID. +- `name: string` - Domain name. +- `url?: string` - Domain URL. +- `environment?: string` - Domain environment. +- `edgeApplicationId: number` - Edge application ID. +- `active: boolean` - Domain status. +- `cnameAccessOnly?: boolean` - CNAME access only. +- `cnames: string[]` - List of CNAMEs. +- `edgeFirewallId?: number` - Edge firewall ID. +- `digitalCertificateId: number | string | null` - Digital certificate ID. +- `mtls?: object | null` - Mutual TLS configuration. +- `mtls.verication: string` - Verification method. Enforce or permissive. +- `mtls.trustedCaCertificateId: number` - Trusted CA certificate ID. +- `mtls.crlList: number[]` - List of CRL IDs. + +### `AzionDomains` + +- `state: 'pending' | 'executed' | 'failed'` - State of the domain list. +- `pages: number` - Number of pages. +- `count: number` - Number of domains. +- `results: AzionDomain[]` - Array of domain objects. + +### `AzionDeleteDomain` + +- `id: number` - Domain ID. +- `state: 'pending' | 'executed' | 'failed'` - State of the domain deletion. + +## Contributing + +Feel free to submit issues or pull requests to improve the functionality or documentation. diff --git a/packages/domains/jest.config.ts b/packages/domains/jest.config.ts new file mode 100644 index 0000000..e0393b4 --- /dev/null +++ b/packages/domains/jest.config.ts @@ -0,0 +1,18 @@ +/** + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +import type { Config } from 'jest'; + +const config: Config = { + displayName: 'Domains', + clearMocks: true, + coverageProvider: 'v8', + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/?(*.)+(spec|test).[tj]s?(x)'], + testPathIgnorePatterns: ['/node_modules/'], +}; + +export default config; diff --git a/packages/domains/package.json b/packages/domains/package.json new file mode 100644 index 0000000..d4f4d4c --- /dev/null +++ b/packages/domains/package.json @@ -0,0 +1,37 @@ +{ + "name": "@lib/domains", + "version": "1.0.0", + "description": "", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "scripts": { + "compile": "tsup --config ../../tsup.config.json", + "lint": "eslint .", + "lint:fix": "eslint --fix .", + "prettier": "prettier --write .", + "test": "jest --clearCache && jest --maxWorkers=1", + "test:watch": "jest -c jest.config.js . --watch", + "test:coverage": "jest --clearCache && jest -c jest.config.js . --coverage" + }, + "types": "./dist/index.d.ts", + "exports": { + ".": { + "require": "./dist/index.js", + "import": "./dist/index.mjs", + "types": "./dist/index.d.ts" + } + }, + "author": "aziontech", + "license": "MIT", + "files": [ + "dist", + "package.json" + ], + "devDependencies": { + "@jest/globals": "^29.7.0", + "@types/jest": "^29.5.12", + "jest": "^29.7.0", + "ts-jest": "^29.1.5", + "ts-node": "^10.9.2" + } +} diff --git a/packages/domains/src/index.test.ts b/packages/domains/src/index.test.ts new file mode 100644 index 0000000..6488f7d --- /dev/null +++ b/packages/domains/src/index.test.ts @@ -0,0 +1,657 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import createClient, { createDomain, deleteDomain, getDomain, getDomains, updateDomain } from './index'; +import * as services from './services/api/index'; + +describe('Domains Package', () => { + const mockToken = 'mock-token'; + const mockDebug = false; + + beforeEach(() => { + jest.clearAllMocks(); + process.env.AZION_TOKEN = mockToken; + process.env.AZION_DEBUG = 'false'; + }); + describe('createDomain', () => { + it('should create a domain', async () => { + const mockResponse = { + results: { + id: '123', + name: 'example.com', + domain_name: 'example.com', + environment: 'production', + is_active: true, + }, + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(services, 'createDomain'); + + const result = await createDomain({ name: 'example.com', edgeApplicationId: 123 }, { debug: mockDebug }); + expect(result).toEqual({ + data: { + state: 'executed', + id: '123', + name: 'example.com', + url: 'example.com', + environment: 'production', + active: true, + }, + }); + expect(services.createDomain).toHaveBeenCalledWith( + mockToken, + { name: 'example.com', edgeApplicationId: 123 }, + { debug: mockDebug }, + ); + }); + + it('should throw an error if the domain creation fails', async () => { + const mockError = new Error('Error creating Domain: Request failed'); + jest.spyOn(global, 'fetch').mockRejectedValue(mockError); + jest.spyOn(services, 'createDomain'); + await expect(createDomain({ name: 'example.com', edgeApplicationId: 123 }, { debug: mockDebug })).rejects.toThrow( + mockError, + ); + expect(services.createDomain).toHaveBeenCalledWith( + mockToken, + { name: 'example.com', edgeApplicationId: 123 }, + { debug: mockDebug }, + ); + }); + + it('should create a domain with all fields', async () => { + const mockResponse = { + results: { + id: '123', + name: 'example.com', + domain_name: 'example.com', + environment: 'production', + is_active: true, + }, + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(services, 'createDomain'); + const domain: any = { + name: 'example.com', + cnames: ['cname1', 'cname2'], + cnameAccessOnly: true, + digitalCertificateId: 'lets_encrypt', + edgeApplicationId: 123, + mtls: { + verification: 'enforce', + trustedCaCertificateId: 123, + crlList: [111], + }, + }; + const { data } = await createDomain(domain, { debug: mockDebug }); + expect(data).toEqual({ + state: 'executed', + id: '123', + name: 'example.com', + url: 'example.com', + environment: 'production', + active: true, + }); + expect(services.createDomain).toHaveBeenCalledWith(mockToken, domain, { debug: mockDebug }); + }); + + it('should an error if the domain edgeApplicationId is not provided', async () => { + jest.spyOn(global, 'fetch').mockResolvedValue({ + json: () => + Promise.resolve({ + edge_application_id: ['This field is required.'], + }), + } as any); + // @eslint-disable-next-line @typescript-eslint/no-explicit-any + await expect(createDomain({ name: 'example.com' } as any, { debug: mockDebug })).resolves.toEqual({ + error: { message: 'Domain name and Edge Application ID are required', operation: 'create domain' }, + }); + }); + + it('should create a domain with status failed', async () => { + const mockResponse = { + detail: 'Invalid Token', + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(services, 'createDomain'); + + const result = await createDomain({ name: 'example.com', edgeApplicationId: 123 }, { debug: mockDebug }); + expect(result).toEqual({ + error: { message: 'Invalid Token', operation: 'create domain' }, + }); + expect(services.createDomain).toHaveBeenCalledWith( + mockToken, + { name: 'example.com', edgeApplicationId: 123 }, + { debug: mockDebug }, + ); + }); + }); + + describe('getDomains', () => { + let mockResponseListDomains: any; + beforeEach(() => { + jest.clearAllMocks(); + process.env.AZION_TOKEN = mockToken; + process.env.AZION_DEBUG = 'false'; + mockResponseListDomains = { + count: 1, + total_pages: 1, + schema_version: 3, + links: { + previous: null, + next: null, + }, + results: [ + { + id: 1700496622, + name: 'My domain', + cnames: [], + cname_access_only: false, + digital_certificate_id: 61561, + edge_application_id: 1700498641, + is_active: true, + domain_name: 'd0ma1n3xmp.map.azionedge.net', + environment: 'production', + is_mtls_enabled: false, + mtls_verification: 'enforce', + mtls_trusted_ca_certificate_id: null, + crl_list: null, + edge_firewall_id: null, + }, + ], + }; + }); + + it('should list domains', async () => { + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponseListDomains) } as any); + jest.spyOn(services, 'getDomains'); + + const results = await getDomains({ debug: mockDebug }); + expect(results.data).toEqual({ + state: 'executed', + pages: 1, + count: 1, + results: expect.arrayContaining([ + expect.objectContaining({ + id: 1700496622, + name: 'My domain', + url: 'd0ma1n3xmp.map.azionedge.net', + environment: 'production', + active: true, + }), + ]), + }); + expect(services.getDomains).toHaveBeenCalledWith(mockToken, { debug: mockDebug }, undefined); + }); + + it('should list domains with all fields', async () => { + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponseListDomains) } as any); + jest.spyOn(services, 'getDomains'); + + const queryParams: any = { orderBy: 'id', page: 1, pageSize: 1, sort: 'asc' }; + const results = await getDomains({ debug: mockDebug }, queryParams); + expect(results.data).toEqual({ + state: 'executed', + pages: 1, + count: 1, + results: expect.arrayContaining([ + expect.objectContaining({ + id: 1700496622, + name: 'My domain', + url: 'd0ma1n3xmp.map.azionedge.net', + environment: 'production', + active: true, + }), + ]), + }); + expect(services.getDomains).toHaveBeenCalledWith(mockToken, { debug: mockDebug }, queryParams); + }); + + it('should list domains with mtls', async () => { + mockResponseListDomains.results[0].is_mtls_enabled = true; + mockResponseListDomains.results[0].mtls_verification = 'enforce'; + mockResponseListDomains.results[0].mtls_trusted_ca_certificate_id = 123; + mockResponseListDomains.results[0].crl_list = [111]; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponseListDomains) } as any); + jest.spyOn(services, 'getDomains'); + + const results = await getDomains({ debug: mockDebug }); + expect(results.data).toEqual({ + state: 'executed', + pages: 1, + count: 1, + results: expect.arrayContaining([ + expect.objectContaining({ + id: 1700496622, + name: 'My domain', + url: 'd0ma1n3xmp.map.azionedge.net', + environment: 'production', + active: true, + mtls: { + verification: 'enforce', + trustedCaCertificateId: 123, + crlList: [111], + }, + }), + ]), + }); + expect(services.getDomains).toHaveBeenCalledWith(mockToken, { debug: mockDebug }, undefined); + }); + }); + + describe('getDomain', () => { + it('should get a domain by id', async () => { + const mockResponse = { + results: { + id: 123, + name: 'example.com', + domain_name: 'example.com', + environment: 'production', + is_active: true, + }, + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(services, 'getDomainById'); + + const result = await getDomain(123, { debug: mockDebug }); + + expect(result).toEqual({ + data: { + state: 'executed', + id: 123, + name: 'example.com', + url: 'example.com', + environment: 'production', + active: true, + }, + }); + expect(services.getDomainById).toHaveBeenCalledWith(mockToken, 123, { debug: mockDebug }); + }); + + it('should get a domain by id with all fields', async () => { + const mockResponse = { + results: { + id: 123, + name: 'example.com', + domain_name: 'example.com', + environment: 'production', + is_active: true, + cnames: ['cname1', 'cname2'], + cname_access_only: true, + digital_certificate_id: 'lets_encrypt', + edge_application_id: 123, + edge_firewall_id: null, + is_mtls_enabled: true, + mtls_verification: 'enforce', + mtls_trusted_ca_certificate_id: 123, + crl_list: [111], + }, + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(services, 'getDomainById'); + + const result = await getDomain(123, { debug: mockDebug }); + expect(result).toEqual({ + data: { + state: 'executed', + id: 123, + name: 'example.com', + url: 'example.com', + environment: 'production', + active: true, + cnames: ['cname1', 'cname2'], + cnameAccessOnly: true, + digitalCertificateId: 'lets_encrypt', + edgeFirewallId: null, + edgeApplicationId: 123, + mtls: { + verification: 'enforce', + trustedCaCertificateId: 123, + crlList: [111], + }, + }, + }); + expect(services.getDomainById).toHaveBeenCalledWith(mockToken, 123, { debug: mockDebug }); + }); + }); + + describe('updateDomain', () => { + it('should update a domain', async () => { + const mockResponse = { + results: { + id: 170, + name: 'Overwritten Domain', + cnames: ['different-cname.org', 'new-domain.net'], + cname_access_only: false, + digital_certificate_id: 61561, + edge_application_id: 123, + is_active: false, + domain_name: 'n3wd0ma1n9.map.azionedge.net', + environment: 'production', + is_mtls_enabled: false, + mtls_verification: 'enforce', + mtls_trusted_ca_certificate_id: null, + crl_list: null, + edge_firewall_id: null, + }, + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(services, 'updateDomain'); + + const result = await updateDomain( + 170, + { name: 'Overwritten Domain', edgeApplicationId: 123, active: false }, + { debug: mockDebug }, + ); + expect(result).toEqual({ + data: { + state: 'executed', + id: 170, + name: 'Overwritten Domain', + url: 'n3wd0ma1n9.map.azionedge.net', + environment: 'production', + active: false, + cnameAccessOnly: false, + cnames: ['different-cname.org', 'new-domain.net'], + digitalCertificateId: 61561, + edgeApplicationId: 123, + edgeFirewallId: null, + mtls: undefined, + }, + }); + expect(services.updateDomain).toHaveBeenCalledWith( + mockToken, + 170, + { name: 'Overwritten Domain', edgeApplicationId: 123, active: false }, + { debug: mockDebug }, + ); + }); + + it('should fail to update a domain if edgeApplicationId is not provided', async () => { + const mockResponse = { + edge_application_id: ['This field is required.'], + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + + const result = await updateDomain( + 170, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { name: 'Overwritten Domain', active: false, edgeApplicationId: undefined } as any, + { debug: mockDebug }, + ); + + expect(result.error).toEqual({ + message: 'Edge Application ID is required', + operation: 'update domain', + }); + }); + + it('should fail to update a domain if the some field is invalid', async () => { + const mockResponse = { + duplicated_domain_name: 'my domain', + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + + const result = await updateDomain(170, { name: 'my domain', edgeApplicationId: 123 }, { debug: mockDebug }); + + expect(result.error).toEqual({ + message: '{"duplicated_domain_name":"my domain"}', + operation: 'update domain', + }); + }); + + it('should update a domain with all fields', async () => { + const mockResponse = { + results: { + id: 170, + name: 'Overwritten Domain', + cnames: ['different-cname.org', 'new-domain.net'], + cname_access_only: false, + digital_certificate_id: 61561, + edge_application_id: 123, + is_active: false, + domain_name: 'n3wd0ma1n9.map.azionedge.net', + environment: 'production', + is_mtls_enabled: true, + mtls_verification: 'enforce', + mtls_trusted_ca_certificate_id: 123, + crl_list: [111], + edge_firewall_id: null, + }, + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(services, 'updateDomain'); + + const domain: any = { + name: 'Overwritten Domain', + cnames: ['different-cname.org', 'new-domain.net'], + cnameAccessOnly: false, + digitalCertificateId: 61561, + edgeApplicationId: 123, + active: false, + mtls: { + verification: 'enforce', + trustedCaCertificateId: 123, + crlList: [111], + }, + }; + const result = await updateDomain(170, domain, { debug: mockDebug }); + expect(result).toEqual({ + data: { + state: 'executed', + id: 170, + name: 'Overwritten Domain', + url: 'n3wd0ma1n9.map.azionedge.net', + environment: 'production', + active: false, + cnameAccessOnly: false, + cnames: ['different-cname.org', 'new-domain.net'], + digitalCertificateId: 61561, + edgeApplicationId: 123, + edgeFirewallId: null, + mtls: { + verification: 'enforce', + trustedCaCertificateId: 123, + crlList: [111], + }, + }, + }); + expect(services.updateDomain).toHaveBeenCalledWith(mockToken, 170, domain, { debug: mockDebug }); + }); + }); + + describe('deleteDomain', () => { + it('should delete a domain', async () => { + jest.spyOn(global, 'fetch').mockResolvedValue({ ok: true, status: 204 } as any); + jest.spyOn(services, 'deleteDomain'); + + const result = await deleteDomain(123, { debug: mockDebug }); + expect(result).toEqual(expect.objectContaining({ data: { state: 'executed', id: 123 } })); + expect(services.deleteDomain).toHaveBeenCalledWith(mockToken, 123, { debug: mockDebug }); + }); + + it('should throw an error if the domain deletion fails with a message', async () => { + jest.spyOn(global, 'fetch').mockResolvedValue({ + ok: true, + status: 404, + json: () => Promise.resolve({ detail: 'Not found.' }), + } as any); + jest.spyOn(services, 'deleteDomain'); + + const result = await deleteDomain(123, { debug: mockDebug }); + expect(result).toEqual(expect.objectContaining({ error: { message: 'Not found.', operation: 'delete domain' } })); + expect(services.deleteDomain).toHaveBeenCalledWith(mockToken, 123, { debug: mockDebug }); + }); + }); + + describe('createClient', () => { + it('should create a client with createDomain', async () => { + const mockResponse = { + results: { + id: '123', + name: 'example.com', + domain_name: 'example.com', + environment: 'production', + is_active: true, + }, + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(services, 'createDomain'); + + const client = createClient({ token: mockToken, options: { debug: mockDebug } }); + + const domain = { name: 'example.com', edgeApplicationId: 123 }; + const resultDomain = await client.createDomain(domain, { debug: mockDebug }); + expect(resultDomain).toEqual({ + data: { + state: 'executed', + id: '123', + name: 'example.com', + url: 'example.com', + environment: 'production', + active: true, + }, + }); + expect(client).toBeDefined(); + }); + + it('should create a client with getDomains', async () => { + const mockResponseListDomains = { + count: 1, + total_pages: 1, + schema_version: 3, + links: { + previous: null, + next: null, + }, + results: [ + { + id: 1700496622, + name: 'My domain', + cnames: [], + cname_access_only: false, + digital_certificate_id: 61561, + edge_application_id: 1700498641, + is_active: true, + domain_name: 'd0ma1n3xmp.map.azionedge.net', + environment: 'production', + is_mtls_enabled: false, + mtls_verification: 'enforce', + mtls_trusted_ca_certificate_id: null, + crl_list: null, + edge_firewall_id: null, + }, + ], + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponseListDomains) } as any); + jest.spyOn(services, 'getDomains'); + + const client = createClient({ token: mockToken, options: { debug: mockDebug } }); + + const results = await client.getDomains({ debug: mockDebug }); + expect(results.data).toEqual({ + state: 'executed', + count: 1, + pages: 1, + results: expect.arrayContaining([ + expect.objectContaining({ + id: 1700496622, + name: 'My domain', + url: 'd0ma1n3xmp.map.azionedge.net', + environment: 'production', + active: true, + }), + ]), + }); + expect(client).toBeDefined(); + }); + + it('should create a client with getDomain', async () => { + const mockResponse = { + results: { + id: 123, + name: 'example.com', + domain_name: 'example.com', + environment: 'production', + is_active: true, + }, + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(services, 'getDomainById'); + + const client = createClient({ token: mockToken, options: { debug: mockDebug } }); + + const result = await client.getDomain(123, { debug: mockDebug }); + expect(result).toEqual({ + data: { + state: 'executed', + id: 123, + name: 'example.com', + url: 'example.com', + environment: 'production', + active: true, + }, + }); + expect(client).toBeDefined(); + }); + + it('should create a client with updateDomain', async () => { + const mockResponse = { + results: { + id: 170, + name: 'Overwritten Domain', + cnames: ['different-cname.org', 'new-domain.net'], + cname_access_only: false, + digital_certificate_id: 61561, + edge_application_id: 123, + is_active: false, + domain_name: 'n3wd0ma1n9.map.azionedge.net', + environment: 'production', + is_mtls_enabled: false, + mtls_verification: 'enforce', + mtls_trusted_ca_certificate_id: null, + crl_list: null, + edge_firewall_id: null, + }, + }; + jest.spyOn(global, 'fetch').mockResolvedValue({ json: () => Promise.resolve(mockResponse) } as any); + jest.spyOn(services, 'updateDomain'); + + const client = createClient({ token: mockToken, options: { debug: mockDebug } }); + + const result = await client.updateDomain( + 170, + { name: 'Overwritten Domain', edgeApplicationId: 123, active: false }, + { debug: mockDebug }, + ); + expect(result).toEqual({ + data: { + state: 'executed', + id: 170, + name: 'Overwritten Domain', + url: 'n3wd0ma1n9.map.azionedge.net', + environment: 'production', + active: false, + cnameAccessOnly: false, + cnames: ['different-cname.org', 'new-domain.net'], + digitalCertificateId: 61561, + edgeApplicationId: 123, + edgeFirewallId: null, + mtls: undefined, + }, + }); + expect(client).toBeDefined(); + }); + + it('should create a client with deleteDomain', async () => { + jest.spyOn(global, 'fetch').mockResolvedValue({ ok: true, status: 204 } as any); + jest.spyOn(services, 'deleteDomain'); + + const client = createClient({ token: mockToken, options: { debug: mockDebug } }); + + const result = await client.deleteDomain(123, { debug: mockDebug }); + expect(result).toEqual({ + data: { id: 123, state: 'executed' }, + }); + expect(client).toBeDefined(); + }); + }); +}); diff --git a/packages/domains/src/index.ts b/packages/domains/src/index.ts new file mode 100644 index 0000000..0d9a16b --- /dev/null +++ b/packages/domains/src/index.ts @@ -0,0 +1,449 @@ +import { createDomain, deleteDomain, getDomainById, getDomains, updateDomain } from './services/api/index'; +import { + AzionClientOptions, + AzionCreateDomain, + AzionDeletedDomain, + AzionDomain, + AzionDomains, + AzionDomainsClient, + AzionDomainsCreateClient, + AzionDomainsResponse, + AzionUpdateDomain, +} from './types'; +import { resolveDebug, resolveToken } from './utils/index'; + +/** + * Create a new domain + * @param token Token to authenticate + * @param domain Domain to create + * @param options Options to create the domain + * @returns Domain created + * @throws Error if domain name or edge application ID are not provided + */ +const createDomainMethod = async ( + token: string, + domain: AzionCreateDomain, + options?: AzionClientOptions, +): Promise> => { + if (domain.name === undefined || domain.edgeApplicationId === undefined) { + return { + error: { + message: 'Domain name and Edge Application ID are required', + operation: 'create domain', + }, + }; + } + const { results: apiResponse, error } = await createDomain(resolveToken(token), domain, { + ...options, + debug: resolveDebug(options?.debug), + }); + if (apiResponse && apiResponse.id) { + return { + data: { + state: 'executed', + id: apiResponse.id, + name: apiResponse?.name, + url: apiResponse?.domain_name, + environment: apiResponse?.environment, + active: apiResponse?.is_active, + }, + }; + } + return { + error: error, + }; +}; + +/** + * Get domains + * @param token Token to authenticate + * @param queryParams Query parameters to list the domains + * @param options Options to list the domains + * @returns List of domains + */ +const getDomainsMethod = async ( + token: string, + queryParams?: { order_by?: 'id' | 'name'; page?: number; pageSize?: number; sort?: 'asc' | 'desc' }, + options?: AzionClientOptions, +): Promise> => { + const apiResponse = await getDomains(resolveToken(token), options, queryParams); + if (apiResponse.results) { + return { + data: { + count: apiResponse.count ?? apiResponse.results.length, + state: 'executed', + results: apiResponse.results, + pages: apiResponse.total_pages ?? 1, + }, + }; + } + return { + error: apiResponse.error, + }; +}; + +/** + * Get a domain by ID + * @param token Token to authenticate + * @param domainId Domain ID + * @param options Options to get the domain + * @returns Domain + */ +const getDomainMethod = async ( + token: string, + domainId: number, + options?: AzionClientOptions, +): Promise> => { + const { results: apiResponse, error } = await getDomainById(resolveToken(token), domainId, { + ...options, + debug: resolveDebug(options?.debug), + }); + if (apiResponse && apiResponse.id) { + return { + data: { + state: 'executed', + id: apiResponse.id, + name: apiResponse?.name, + url: apiResponse?.domain_name, + environment: apiResponse?.environment, + active: apiResponse?.is_active, + cnameAccessOnly: apiResponse?.cname_access_only, + digitalCertificateId: apiResponse?.digital_certificate_id, + cnames: apiResponse?.cnames, + edgeApplicationId: apiResponse?.edge_application_id, + edgeFirewallId: apiResponse?.edge_firewall_id, + mtls: apiResponse?.is_mtls_enabled + ? { + verification: apiResponse.mtls_verification as 'enforce' | 'permissive', + trustedCaCertificateId: apiResponse.mtls_trusted_ca_certificate_id as number, + crlList: apiResponse.crl_list, + } + : undefined, + }, + }; + } + return { + error: error, + }; +}; + +/** + * + * @param token The token to authenticate + * @param domainId The domain ID + * @param domain The domain to update + * @param options Options to update the domain + * @returns Domain updated + */ +const updateDomainMethod = async ( + token: string, + domainId: number, + domain: AzionUpdateDomain, + options?: AzionClientOptions, +): Promise> => { + if (domain?.edgeApplicationId === undefined) { + return { + error: { + message: 'Edge Application ID is required', + operation: 'update domain', + }, + }; + } + + const apiResponse = await updateDomain(resolveToken(token), domainId, domain, { + ...options, + debug: resolveDebug(options?.debug), + }); + + if (!apiResponse?.results?.id) { + return { + error: apiResponse?.error, + }; + } + + return { + data: { + state: 'executed', + name: apiResponse?.results?.name, + id: apiResponse?.results?.id, + environment: apiResponse?.results?.environment, + cnames: apiResponse?.results?.cnames, + url: apiResponse?.results?.domain_name, + active: apiResponse?.results?.is_active, + cnameAccessOnly: apiResponse?.results?.cname_access_only, + digitalCertificateId: apiResponse?.results?.digital_certificate_id, + edgeApplicationId: apiResponse?.results?.edge_application_id, + edgeFirewallId: apiResponse?.results?.edge_firewall_id, + mtls: apiResponse?.results?.is_mtls_enabled + ? { + verification: apiResponse?.results.mtls_verification as 'enforce' | 'permissive', + trustedCaCertificateId: apiResponse?.results.mtls_trusted_ca_certificate_id as number, + crlList: apiResponse?.results.crl_list, + } + : undefined, + }, + }; +}; + +/** + * Delete a domain + * @param token Token to authenticate + * @param domainId Domain ID + * @param options Options to delete the domain + * @returns Domain deleted + */ +const deleteDomainMethod = async ( + token: string, + domainId: number, + options?: AzionClientOptions, +): Promise> => { + const { error } = await deleteDomain(resolveToken(token), domainId, { + ...options, + debug: resolveDebug(options?.debug), + }); + if (error) { + return { + error: error, + }; + } + return { + data: { + state: 'executed', + id: domainId, + }, + }; +}; + +/** + * Create a new domain + * @param domain Domain to create + * @param options Options to create the domain + * @returns Domain created with data or error + * + * @example + * const domain = { name: 'example.com', edgeApplicationId: 123 }; + * const { data, error } = await createDomain(domain); + * if(data) { + * console.log(data); + * } else { + * console.error(error); + * + * + */ +const createDomainWrapper = async ( + domain: AzionCreateDomain, + options?: AzionClientOptions, +): Promise> => { + return createDomainMethod(resolveToken(), domain, options); +}; + +/** + * List domains + * @param options Options to list the domains + * @param queryParams Query parameters to list the domains + * @returns List of domains with data or error + * + * @example + * const { data, error } = await getDomains(); + * if(data) { + * console.log(data.results); + * } else { + * console.error(error); + * } + */ +const getDomainsWrapper = async ( + options?: AzionClientOptions, + queryParams?: { orderBy?: 'id' | 'name'; page?: number; pageSize?: number; sort?: 'asc' | 'desc' }, +): Promise> => { + return getDomainsMethod(resolveToken(), queryParams, options); +}; + +/** + * Get a domain by ID + * @param domainId Domain ID + * @param options Options to get the domain + * @returns Domain with data or error + * + * @example + * const { data, error } = await getDomain(123); + * if(data) { + * console.log(data); + * } else { + * console.error(error); + * + */ +const getDomainWrapper = async ( + domainId: number, + options?: AzionClientOptions, +): Promise> => { + return getDomainMethod(resolveToken(), domainId, options); +}; + +/** + * Update a domain + * @param domainId Domain ID + * @param domain Domain to update + * @param options Options to update the domain + * @returns Domain updated with data or error + * + * @example + * const domain = { name: 'example.com', edgeApplicationId: 123 }; + * const { data, error } = await updateDomain(123, domain); + * if(data) { + * console.log(data); + * } else { + * console.error(error); + * } + */ +const updateDomainWrapper = async ( + domainId: number, + domain: AzionUpdateDomain, + options?: AzionClientOptions, +): Promise> => { + return updateDomainMethod(resolveToken(), domainId, domain, options); +}; + +/** + * Delete a domain + * @param domainId Domain ID + * @param options Options to delete the domain + * @returns Domain deleted with data or error + * + * @example + * const { data, error } = await deleteDomain(123); + * if(data) { + * console.log(data.id); + * } else { + * console.error(error); + * + */ +const deleteDomainWrapper = async ( + domainId: number, + options?: AzionClientOptions, +): Promise> => { + return deleteDomainMethod(resolveToken(), domainId, options); +}; + +/** + * Creates an Azion client to interact with the Domains. + * + * @param {Partial<{ token?: string; options?: AzionClientOptions; }>} [config] - Configuration options for the client. + * @returns {AzionDomainsClient} An object with methods to interact with the Domains. + * + * @example + * const client = createClient(); // Uses the token from the environment variable process.env.AZION_TOKEN + * const domain = { name: 'example.com', edgeApplicationId: 123 }; + * const result = await client.createDomain(domain); + * console.log(result); + */ +const createClient: AzionDomainsCreateClient = ( + config?: Partial<{ token?: string; options?: AzionClientOptions }>, +): AzionDomainsClient => { + const tokenValue = resolveToken(config?.token); + const debugValue = resolveDebug(config?.options?.debug); + + const client: AzionDomainsClient = { + /** + * Create a new domain + * @param domain Domain to create + * @param options Options to create the domain + * @returns Domain created with data or error + * + * @example + * const domain = { name: 'example.com', edgeApplicationId: 123 }; + * const { data, error } = await client.createDomain(domain); + * if(data) { + * console.log(data); + * } else { + * console.error(error); + * } + */ + createDomain: (domain: AzionCreateDomain, options?: AzionClientOptions) => + createDomainMethod(tokenValue, domain, { ...options, debug: debugValue }), + + /** + * List domains + * @param options Options to list the domains + * @param queryParams Query parameters to list the domains + * @returns List of domains with data or error + * + * @example + * const { data, error } = await client.getDomains(); + * if(data) { + * console.log(data.results); + * } else { + * console.error(error); + * } + */ + getDomains: ( + options?: AzionClientOptions, + queryParams?: { orderBy?: 'id' | 'name'; page?: number; pageSize?: number; sort?: 'asc' | 'desc' }, + ) => getDomainsMethod(tokenValue, queryParams, { ...options, debug: debugValue }), + + /** + * Get a domain by ID + * @param domainId Domain ID + * @param options Options to get the domain + * @returns Domain created with data or error + * + * @example + * const { data, error } = await client.getDomain(123); + * if(data) { + * console.log(data); + * } else { + * console.error(error); + * } + */ + getDomain: (domainId: number, options?: AzionClientOptions) => + getDomainMethod(tokenValue, domainId, { ...options, debug: debugValue }), + + /** + * Update a domain + * @param domainId Domain ID + * @param domain Domain to update + * @param options Options to update the domain + * @returns Domain updated with data or error + * + * @example + * const domain = { name: 'example.com', edgeApplicationId: 123 }; + * const { data, error } = await client.updateDomain(123, domain); + * if(data) { + * console.log(data); + * } else { + * console.error(error); + * } + */ + updateDomain: (domainId: number, domain: AzionUpdateDomain, options?: AzionClientOptions) => + updateDomainMethod(tokenValue, domainId, domain, { ...options, debug: debugValue }), + + /** + * Delete a domain + * @param domainId Domain ID + * @param options Options to delete the domain + * @returns Domain deleted with data or error + * @example + * const { data, error } = await client.deleteDomain(123); + * if(data) { + * console.log(data.id); + * } else { + * console.error(error); + * } + */ + deleteDomain: (domainId: number, options?: AzionClientOptions) => + deleteDomainMethod(tokenValue, domainId, { ...options, debug: debugValue }), + }; + return client; +}; + +export { + createClient, + createDomainWrapper as createDomain, + deleteDomainWrapper as deleteDomain, + getDomainWrapper as getDomain, + getDomainsWrapper as getDomains, + updateDomainWrapper as updateDomain, +}; + +export default createClient; + +export type * from './types'; diff --git a/packages/domains/src/services/api/index.ts b/packages/domains/src/services/api/index.ts new file mode 100644 index 0000000..9e05c59 --- /dev/null +++ b/packages/domains/src/services/api/index.ts @@ -0,0 +1,226 @@ +import { AzionClientOptions, AzionDomain } from '../../types'; +import { + ApiAzionDomainResponse, + ApiAzionDomainResult, + ApiAzionListDomainsResponse, + ApiAzionQueryListDomainsResponse, +} from './types'; + +const BASE_URL = + process.env.AZION_ENV === 'stage' ? 'https://stage-api.azion.net/domains' : 'https://api.azionapi.net/domains'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const handleApiError = (fields: string[], data: any) => { + let error = undefined; + fields.forEach((field: string) => { + if (data[field]) { + const message = Array.isArray(data[field]) ? data[field].join(', ') : data[field]; + error = message; + } + }); + return error ?? JSON.stringify(data); +}; + +const makeHeaders = (token: string) => ({ + Authorization: `Token ${token}`, + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', +}); + +const createDomain = async ( + token: string, + domain: AzionDomain, + { debug }: AzionClientOptions, +): Promise => { + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let body: any = { + name: domain.name, + cnames: domain?.cnames ?? [], + cname_access_only: domain?.cnameAccessOnly ?? false, + digital_certificate_id: domain?.digitalCertificateId ?? undefined, + edge_application_id: domain.edgeApplicationId, + edge_firewall_id: domain?.edgeFirewallId ?? undefined, + is_active: true, + }; + + if (domain?.mtls) { + if (domain.mtls.verification !== 'enforce' && domain.mtls.verification !== 'permissive') { + throw new Error('mtls.verification must be enforce or permissive'); + } + body = { + ...body, + is_mtls_enabled: true, + mtls_verification: domain.mtls.verification, + mtls_trusted_ca_certificate_id: domain.mtls.trustedCaCertificateId, + crl_list: domain.mtls.crlList, + }; + } + + const response = await fetch(BASE_URL, { + method: 'POST', + headers: makeHeaders(token), + body: JSON.stringify(body), + }); + const data = await response.json(); + if (debug) console.log('Response Post Domains', data); + if (!data.results) { + return { + error: { message: handleApiError(['detail', 'edge_application_id'], data), operation: 'create domain' }, + }; + } + return data; + } catch (error) { + if (debug) console.error('Error creating Domain:', error); + throw error; + } +}; + +const getDomains = async ( + token: string, + options?: AzionClientOptions, + queryParams?: ApiAzionQueryListDomainsResponse, +): Promise => { + try { + const { page_size = 10, page = 1 } = queryParams || {}; + const queryParamsUrl = new URLSearchParams({ page_size: String(page_size), page: String(page) }); + const response = await fetch(`${BASE_URL}?${queryParamsUrl.toString()}`, { + method: 'GET', + headers: makeHeaders(token), + }); + const data = await response.json(); + if (options?.debug) console.log('Response List Domains', data); + if (!data.results) { + return { + error: { message: handleApiError(['detail', 'edge_application_id'], data), operation: 'list domains' }, + }; + } + return { + ...data, + results: data.results.map((domain: ApiAzionDomainResponse) => ({ + id: domain.id, + name: domain.name, + url: domain.domain_name, + environment: domain.environment, + active: domain.is_active, + edgeApplicationId: domain.edge_application_id, + cnameAccessOnly: domain.cname_access_only, + digitalCertificateId: domain.digital_certificate_id, + edgeFirewallId: domain?.edge_firewall_id, + cnames: domain.cnames, + mtls: domain.is_mtls_enabled + ? { + verification: domain.mtls_verification as 'enforce' | 'permissive', + trustedCaCertificateId: domain.mtls_trusted_ca_certificate_id as number, + crlList: domain.crl_list, + } + : undefined, + })), + }; + } catch (error) { + if (options?.debug) console.error('Error listing Domains:', error); + throw error; + } +}; + +const getDomainById = async ( + token: string, + domainId: number, + options?: AzionClientOptions, +): Promise => { + try { + const response = await fetch(`${BASE_URL}/${domainId}`, { + method: 'GET', + headers: makeHeaders(token), + }); + const data = await response.json(); + if (options?.debug) console.log('Response Get Domain', data); + if (!data.results) { + return { + error: { message: handleApiError(['detail', 'edge_application_id'], data), operation: 'get domain' }, + }; + } + return data; + } catch (error) { + if (options?.debug) console.error('Error getting Domain:', error); + throw error; + } +}; + +const updateDomain = async ( + token: string, + domainId: number, + domain: AzionDomain, + options?: AzionClientOptions, +): Promise => { + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let body: any = { + name: domain.name, + cnames: domain?.cnames ?? [], + cname_access_only: domain?.cnameAccessOnly ?? false, + digital_certificate_id: domain?.digitalCertificateId ?? undefined, + edge_application_id: domain.edgeApplicationId, + edge_firewall_id: domain?.edgeFirewallId ?? undefined, + is_active: domain.active ?? true, + }; + + if (domain?.mtls) { + if (domain.mtls.verification !== 'enforce' && domain.mtls.verification !== 'permissive') { + throw new Error('mtls.verification must be enforce or permissive'); + } + body = { + ...body, + is_mtls_enabled: true, + mtls_verification: domain.mtls.verification, + mtls_trusted_ca_certificate_id: domain.mtls.trustedCaCertificateId, + crl_list: domain.mtls.crlList, + }; + } + + const response = await fetch(`${BASE_URL}/${domainId}`, { + method: 'PUT', + headers: makeHeaders(token), + body: JSON.stringify(body), + }); + const data = await response.json(); + if (options?.debug) console.log('Response Put Domains', data); + if (!data.results) { + return { + error: { message: handleApiError(['detail', 'edge_application_id'], data), operation: 'update domain' }, + }; + } + return data; + } catch (error) { + if (options?.debug) console.error('Error updating Domain:', error); + throw error; + } +}; + +const deleteDomain = async ( + token: string, + domainId: number, + options?: AzionClientOptions, +): Promise => { + try { + const result = await fetch(`${BASE_URL}/${domainId}`, { + method: 'DELETE', + headers: makeHeaders(token), + }); + + if (result.status !== 204) { + const message = await result.json(); + return { + error: { message: handleApiError(['detail', 'edge_application_id'], message), operation: 'delete domain' }, + }; + } + + if (options?.debug) console.log('Response Delete Domain'); + return { results: undefined }; + } catch (error) { + if (options?.debug) console.error('Error deleting Domain:', error); + throw error; + } +}; + +export { createDomain, deleteDomain, getDomainById, getDomains, updateDomain }; diff --git a/packages/domains/src/services/api/types.ts b/packages/domains/src/services/api/types.ts new file mode 100644 index 0000000..9a88a3e --- /dev/null +++ b/packages/domains/src/services/api/types.ts @@ -0,0 +1,44 @@ +export type ApiAzionDomainResponse = { + id: number; + domain_name: string; + environment: string; + name: string; + cnames: string[]; + cname_access_only?: boolean; + digital_certificate_id: string | null | undefined; + edge_application_id?: number; + edge_firewall_id?: number; + is_active: boolean; + is_mtls_enabled?: boolean; + mtls_verification?: string; + mtls_trusted_ca_certificate_id: number; + crl_list?: number[]; +}; + +export type ApiError = { + message: string; + operation: string; +}; + +export type ApiAzionDomainResult = { + results?: ApiAzionDomainResponse; + error?: ApiError; +}; + +export type ApiAzionListDomainsResponse = { + count?: number; + total_pages?: number; + links?: { + previous: string | null; + next: string | null; + }; + results?: ApiAzionDomainResponse[]; + error?: ApiError; +}; + +export type ApiAzionQueryListDomainsResponse = { + order_by?: 'id' | 'name'; + page?: number; + page_size?: number; + sort?: 'asc' | 'desc'; +}; diff --git a/packages/domains/src/types.ts b/packages/domains/src/types.ts new file mode 100644 index 0000000..d86fc76 --- /dev/null +++ b/packages/domains/src/types.ts @@ -0,0 +1,227 @@ +/** + * AzionDomainsResponse is a generic type that represents the response of the Azion API. + * It can be either a success response with the data or an error response with the error message and operation. + * + * @param T The type of the data that the response contains. + * @returns An object with the data or an error message and operation. + */ +export type AzionDomainsResponse = { + data?: T; + error?: { + message: string; + operation: string; + }; +}; + +/** + * ResponseState is a type that represents the state of the response of the Azion API. + * It can be either pending, executed or failed. + * @returns A string with the state of the response. + */ +export type ResponseState = 'pending' | 'executed' | 'failed'; + +/** + * AzionDomain is a type that represents the domain object in the Azion API. + * @param state The state of the response. + * @param id The id of the domain. + * @param url The url of the domain. + * @param environment The environment of the domain. + * @param active The status of the domain. + * @param name The name of the domain. + * @param cnameAccessOnly The status of the domain. + * @param cnames The cnames of the domain. + * @param edgeApplicationId The id of the edge application. + * @param edgeFirewallId The id of the edge firewall. + * @param digitalCertificateId The id of the digital certificate. + * @param mtls The mtls object. + * @returns An object with the domain data. + */ +export type AzionDomain = { + state?: ResponseState; + id?: number; + url?: string; + environment?: string; + active?: boolean; + name: string; + cnameAccessOnly?: boolean; + cnames?: string[]; + edgeApplicationId?: number; + edgeFirewallId?: number; + digitalCertificateId?: string | number | null; + mtls?: { + verification: 'enforce' | 'permissive'; + trustedCaCertificateId: number; + crlList?: number[]; + }; +}; + +/** + * AzionDomains is a type that represents the domains object in the Azion API. + * @param state The state of the response. + * @param count The count of the domains. + * @param pages The number of pages. + * @param results The list of domains. + * @returns An object with the domains data. + */ +export type AzionCreateDomain = Omit< + AzionDomain, + 'id' | 'environment' | 'active' | 'url' | 'state' | 'edgeApplicationId' +> & { + edgeApplicationId: number; +}; + +/** + * AzionUpdateDomain is a type that represents the domain object in the Azion API. + * @param active The status of the domain. + * @param name The name of the domain. + * @param cnameAccessOnly The status of the domain. + * @param cnames The cnames of the domain. + * @param edgeApplicationId The id of the edge application. + * @param edgeFirewallId The id of the edge firewall. + * @param digitalCertificateId The id of the digital certificate. + * @param mtls The mtls object. + * @returns An object with the domain data. + */ +export type AzionUpdateDomain = Omit & { + edgeApplicationId: number; +}; + +/** + * AzionDeletedDomain is a type that represents the domain object in the Azion API. + * @param id The id of the domain. + * @param state The state of the response. + * @returns An object with the domain data. + */ +export type AzionDeletedDomain = Pick; + +/** + * AzionClientOptions is a type that represents the options of the Azion API client. + * @param debug The debug option. + * @param force The force option. + * @returns An object with the options of the client. + */ +export type AzionClientOptions = { + debug?: boolean | undefined; + force?: boolean | undefined; +}; + +/** + * AzionDomains is a type that represents the domains object in the Azion API. + * @param state The state of the response. + * @param count The count of the domains. + * @param pages The number of pages. + * @param results The list of domains. + * @returns An object with the domains data. + */ +export type AzionDomains = { + state: ResponseState; + count: number; + pages: number; + results: AzionDomain[]; +}; + +/** + * AzionDomainsCreateClient is a type that represents the function to create an Azion domains client. + * @param config The configuration object. + * @returns The Azion domains client. + */ +export type AzionDomainsCreateClient = ( + config?: Partial<{ token?: string; options?: AzionClientOptions }>, +) => AzionDomainsClient; +/** + * AzionDomainsClient is a type that represents the client of the Azion domains API. + * @param createDomain The function to create a domain. + * @param getDomains The function to get the domains. + * @param getDomain The function to get a domain. + * @param updateDomain The function to update a domain. + * @param deleteDomain The function to delete a domain. + * @returns An object with the client functions. + */ +export interface AzionDomainsClient { + /** + * createDomain is a function that creates a domain in the Azion API. + * @param {AzionCreateDomain} domain The domain object. + * @param { AzionClientOptions } options The options of the client. + * @returns {Promise>} The response of the API. + * @example + * const domain = { + * name: 'example.com', + * edgeApplicationId: 1, + * }; + * const { data, error } = await client.createDomain(domain); + * if (error) { + * console.error(error.message); + * } else { + * console.log(data); + * } + */ + createDomain: (domain: AzionCreateDomain, options?: AzionClientOptions) => Promise>; + /** + * getDomains is a function that gets the domains in the Azion API. + * @param {AzionClientOptions} options The options of the client. + * @param {{ orderBy?: 'id' | 'name'; page?: number; pageSize?: number; sort?: 'asc' | 'desc' }} queryParams The query parameters of the request. + * @returns {Promise>} The response of the API. + * @example + * const { data, error } = await client.getDomains(); + * if (error) { + * console.error(error.message); + * } else { + * console.log(data.results); + * } + */ + getDomains: ( + options?: AzionClientOptions, + queryParams?: { orderBy?: 'id' | 'name'; page?: number; pageSize?: number; sort?: 'asc' | 'desc' }, + ) => Promise>; + /** + * getDomain is a function that gets a domain in the Azion API. + * @param {number} domainId The id of the domain. + * @param {AzionClientOptions} options The options of the client. + * @returns {Promise>} The response of the API. + * @example + * const { data, error } = await client.getDomain(1); + * if (error) { + * console.error(error.message); + * } else { + * console.log(data); + * } + */ + getDomain: (domainId: number, options?: AzionClientOptions) => Promise>; + /** + * updateDomain is a function that updates a domain in the Azion API. + * @param {number} domainId The id of the domain. + * @param {AzionUpdateDomain} domain The domain object. + * @param {AzionClientOptions} options The options of the client. + * @returns {Promise>} The response of the API. + * @example + * const domain = { + * name: 'example.com', + * edgeApplicationId: 1, + * }; + * const { data, error } = await client.updateDomain(1, domain); + * if (error) { + * console.error(error.message); + * } else { + * console.log(data); + * } + */ + updateDomain: ( + domainId: number, + domain: AzionUpdateDomain, + options?: AzionClientOptions, + ) => Promise>; + /** + * deleteDomain is a function that deletes a domain in the Azion API. + * @param {number} domainId The id of the domain. + * @param {AzionClientOptions} options The options of the client. + * @returns {Promise>} The response of the API. + * @example + * const { data, error } = await client.deleteDomain(1); + * if (error) { + * console.error(error.message); + * } else { + * console.log(data.id); + * } + */ + deleteDomain: (domainId: number, options?: AzionClientOptions) => Promise>; +} diff --git a/packages/domains/src/utils/index.test.ts b/packages/domains/src/utils/index.test.ts new file mode 100644 index 0000000..fcf67a1 --- /dev/null +++ b/packages/domains/src/utils/index.test.ts @@ -0,0 +1,103 @@ +import { limitArraySize, resolveDebug, resolveToken } from './index'; + +describe('Utils', () => { + describe('resolveToken', () => { + const mockToken = 'mockToken'; + beforeEach(() => { + process.env.AZION_TOKEN = mockToken; + }); + it('should successfully resolveToken by env', () => { + const result = resolveToken(); + expect(result).toEqual(mockToken); + }); + + it('should successfully resolveToken by parameter', () => { + const token = 'token-param'; + const result = resolveToken(token); + expect(result).toEqual(token); + }); + + it('should successfully resolveToken with parameter empty', () => { + process.env.AZION_TOKEN = ''; + const result = resolveToken(''); + expect(result).toEqual(''); + }); + + it('should if no token is provided and AZION_TOKEN is not set', () => { + delete process.env.AZION_TOKEN; + const result = resolveToken(); + expect(result).toBe(''); + }); + }); + + describe('resolveDebug', () => { + afterEach(() => { + delete process.env.AZION_DEBUG; + }); + + it('should return true when debug is not provided and AZION_DEBUG is true', () => { + process.env.AZION_DEBUG = 'true'; + const result = resolveDebug(); + expect(result).toBe(true); + }); + + it('should return false when debug is not provided and AZION_DEBUG is false', () => { + process.env.AZION_DEBUG = 'false'; + const result = resolveDebug(); + expect(result).toBe(false); + }); + + it('should return true when debug is true', () => { + const result = resolveDebug(true); + expect(result).toBe(true); + }); + + it('should return false when debug is false', () => { + const result = resolveDebug(false); + expect(result).toBe(false); + }); + + it('should return false when debug is not provided and AZION_DEBUG is not set', () => { + const result = resolveDebug(); + expect(result).toBe(false); + }); + + it('should return true when debug is true and AZION_DEBUG is false', () => { + process.env.AZION_DEBUG = 'false'; + const result = resolveDebug(true); + expect(result).toBe(true); + }); + + it('should return false when debug is false and AZION_DEBUG is true', () => { + process.env.AZION_DEBUG = 'true'; + const result = resolveDebug(false); + expect(result).toBe(false); + }); + + it('should return true when debug is true and AZION_DEBUG is true', () => { + process.env.AZION_DEBUG = 'true'; + const result = resolveDebug(true); + expect(result).toBe(true); + }); + }); + + describe('LimitArraySize', () => { + it('should return the same array if it is smaller than the limit', () => { + const mockArray = [1, 2, 3]; + const result = limitArraySize(mockArray, 4); + expect(result).toEqual(mockArray); + }); + + it('should return a limited array if it is bigger than the limit', () => { + const mockArray = [1, 2, 3]; + const result = limitArraySize(mockArray, 2); + expect(result).toEqual([1, 2]); + }); + + it('should return an empty array if the input is empty', () => { + const mockArray: number[] = []; + const result = limitArraySize(mockArray, 2); + expect(result).toEqual([]); + }); + }); +}); diff --git a/packages/domains/src/utils/index.ts b/packages/domains/src/utils/index.ts new file mode 100644 index 0000000..e528631 --- /dev/null +++ b/packages/domains/src/utils/index.ts @@ -0,0 +1,33 @@ +/** + * Resolve the token from the environment variable or the parameter + * @param token Token to resolve + * @returns Resolved token + * + */ +export const resolveToken = (token?: string) => token ?? process.env.AZION_TOKEN ?? ''; + +/** + * Resolve the debug flag from the environment variable or the parameter + * @param debug Debug flag to resolve + * @returns Resolved debug flag + */ +export const resolveDebug = (debug?: boolean) => { + const envDebugFlag = process.env.AZION_DEBUG && process.env.AZION_DEBUG === 'true'; + if (typeof debug === 'undefined' || debug === null) { + return !!envDebugFlag; + } + return !!debug; +}; + +/** + * Limit the size of an array + * @param array Array to limit + * @param limit Limit of the array + * @returns Array with limited size + */ +export const limitArraySize = (array: T[], limit: number): T[] => { + if (array?.length > limit) { + return array.slice(0, limit); + } + return array; +}; diff --git a/packages/domains/tsconfig.json b/packages/domains/tsconfig.json new file mode 100644 index 0000000..c589e3b --- /dev/null +++ b/packages/domains/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "ESNext", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "declaration": true + } +} From 1e9411adc0a1bb43d3de8e45adc8c3cfdb49a98d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 10 Sep 2024 13:43:47 +0000 Subject: [PATCH 34/45] chore(release): 1.7.0-stage.11 [skip ci] ## [1.7.0-stage.11](https://github.com/aziontech/lib/compare/v1.7.0-stage.10...v1.7.0-stage.11) (2024-09-10) ### Features * add package domains (#38) ([9a020c1](https://github.com/aziontech/lib/commit/9a020c14907f65009da97dc507fe901017452fe6)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2bddaf..5c57388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.7.0-stage.11](https://github.com/aziontech/lib/compare/v1.7.0-stage.10...v1.7.0-stage.11) (2024-09-10) + + +### Features + +* add package domains (#38) ([9a020c1](https://github.com/aziontech/lib/commit/9a020c14907f65009da97dc507fe901017452fe6)) + ## [1.7.0-stage.10](https://github.com/aziontech/lib/compare/v1.7.0-stage.9...v1.7.0-stage.10) (2024-09-09) diff --git a/package.json b/package.json index 6bc9532..15dcdcf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.7.0-stage.10", + "version": "1.7.0-stage.11", "description": "Azion Packages for Edge Computing.", "bin": { "azion": "./bin/azion" From 7a668e61cdd67a0d75375b18e5520a7a18ed2aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 16 Sep 2024 15:16:33 -0300 Subject: [PATCH 35/45] docs: simplify docs (#39) * refactor: bucketcollection type * docs: simplify docs --- README.md | 442 ++++++-------------------------- packages/applications/README.md | 0 packages/client/README.MD | 10 +- packages/storage/README.md | 12 +- packages/storage/src/index.ts | 12 +- packages/storage/src/types.ts | 8 +- 6 files changed, 105 insertions(+), 379 deletions(-) create mode 100644 packages/applications/README.md diff --git a/README.md b/README.md index 74e42f9..d8cd10a 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,16 @@ These libraries are designed to be versatile and can be used both within and out - [Installation](#installation) - [Products](#products) - [Client](#client) - - [Storage](#storage) - - [SQL](#sql) - - [Purge](#purge) - - [Domains](#domains) -- [Utilities](#utilities) - - [Cookies](#cookies) - - [Jwt](#jwt) - - [WASM Image Processor](#wasm-image-processor) - - [Utils](#utils) + - [Storage](./packages/storage/README.MD) + - [SQL](./packages/sql/README.MD) + - [Purge](./packages/purge/README.MD) + - [Domains](./packages/domains/README.MD) + - [Applications](./packages/applications/README.MD) +- Utilities + - [Cookies](./packages/cookies/README.MD) + - [Jwt](./packages/jwt/README.MD) + - [WASM Image Processor](./packages/wasm-image-processor/README.MD) + - [Utils](./packages/utils/README.MD) - [Types](#types) - [AzionConfig](#config) - [Contributing](#contributing) @@ -38,416 +39,129 @@ yarn add azion ## Products -### Client - -The Azion Client provides a unified interface to interact with all Azion services. +### Using the Client vs. Independent Package Functions -#### Examples - -**JavaScript:** +The Azion client provides a unified interface to interact with all products and services. You can use the client to access and manage all functionalities across Storage, SQL, Purge, and more. When using the client, you can pass configurations (e.g., `token`, `debug`) explicitly as parameters. ```javascript import { createClient } from 'azion'; const client = createClient({ token: 'your-api-token', debug: true }); -// Storage -const newBucket = await client.storage.createBucket('my-new-bucket', 'public'); -console.log(`Bucket created with name: ${newBucket.name}`); - -const { data: allBuckets } = await client.storage.getBuckets(); -console.log(`Retrieved ${allBuckets.count} buckets`); - -// SQL -const { data: newDatabase } = await client.sql.createDatabase('my-new-db'); -console.log(`Database created with ID: ${newDatabase.id}`); - -const { data: allDatabases } = await client.sql.getDatabases(); -console.log(`Retrieved ${allDatabases.count} databases`); - -// Purge -const purgeResult = await client.purge.purgeURL(['http://example.com/image.jpg']); -console.log(`Purge successful: ${purgeResult.items}`); -``` - -**TypeScript:** - -```typescript -import { createClient } from 'azion'; -import type { AzionClient, Bucket, Purge } from 'azion/client'; -import type { AzionDatabaseResponse, AzionDatabaseQueryResponse, AzionBuckets } from 'azion/sql'; - -const client: AzionClient = createClient({ token: 'your-api-token', debug: true }); - -// Storage -const newBucket: Bucket | null = await client.storage.createBucket('my-new-bucket', 'public'); -console.log(`Bucket created with name: ${newBucket.name}`); - -const { data: allBuckets }: AzionStorageResponse = await client.storage.getBuckets(); -console.log(`Retrieved ${allBuckets.count} buckets`); - -// SQL -const { data: newDatabase }: AzionDatabaseResponse = await client.sql.createDatabase('my-new-db'); -console.log(`Database created with ID: ${newDatabase.id}`); - -const { data: allDatabases }: AzionDatabaseResponse = await client.sql.getDatabases(); -console.log(`Retrieved ${allDatabases.count} databases`); - -// Purge -const purgeResult: Purge | null = await client.purge.purgeURL(['http://example.com/image.jpg']); -console.log(`Purge successful: ${purgeResult.items}`); -``` - -### Storage - -The Storage library provides methods to interact with Azion Edge Storage. - -#### Examples - -**JavaScript:** - -```javascript -import { createClient } from 'azion/storage'; - -const client = createClient({ token: 'your-api-token', debug: true }); - -const { data, error } = await client.createBucket('my-new-bucket', 'public'); +// Example: Creating a database via the client +const { data: newDatabase, error } = await client.sql.createDatabase('my-new-database'); if (data) { - console.log(`Bucket created with name: ${data.name}`); -} - -const { data: allBuckets } = await client.getBuckets(); -if (allBuckets) { - console.log(`Retrieved ${allBuckets.count} buckets`); + console.log(`Database created with ID: ${newDatabase.id}`); +} else { + console.error('Failed to create database', error); } ``` -**TypeScript:** +Alternatively, if you prefer to use individual functions directly from each package, you need to configure tokens and settings via environment variables (e.g., using a `.env` file). Each module has its own internal client that manages the interaction. + +Example with explicit client for a specific module: ```typescript -import { createClient } from 'azion/storage'; -import type { AzionStorageClient, AzionStorageResponse, AzionBucket, AzionBuckets } from 'azion/storage'; +import { createClient, StorageClient } from 'azion/storage'; -const client: AzionStorageClient = createClient({ token: 'your-api-token', debug: true }); +const client: StorageClient = createClient({ token: 'your-api-token', debug: true }); + +const { data, error }: AzionStorageResponse = await client.createBucket({ + name: 'my-new-bucket', + edge_access: 'public', +}); -const { data, error }: AzionStorageResponse = await client.createBucket('my-new-bucket', 'public'); if (data) { console.log(`Bucket created with name: ${data.name}`); -} - -const { data: allBuckets }: AzionStorageResponse = await client.getBuckets(); -if (allBuckets) { - console.log(`Retrieved ${allBuckets.count} buckets`); +} else { + console.error('Failed to create bucket', error); } ``` -Read more in the [Storage README](./packages/storage/README.md). - -### SQL - -The SQL library provides methods to interact with Azion Edge SQL databases. - -#### Examples - -**JavaScript:** +You can also use individual functions without any client by importing them directly from the package. This approach requires environment variables for configuration: ```javascript -import { createClient } from 'azion/sql'; - -const client = createClient({ token: 'your-api-token', debug: true }); - -const { data: newDatabase, error } = await client.createDatabase('my-new-db'); -if (newDatabase) { - console.log(`Database created with ID: ${newDatabase.id}`); -} - -const { data, error } = await client.getDatabases(); -if (data) { - console.log(`Retrieved ${data.length} databases`); -} -``` - -**TypeScript:** +import { createDatabase } from 'azion/sql'; -```typescript -import { createClient } from 'azion/sql'; -import type { AzionSQLClient, AzionDatabaseResponse } from 'azion/sql'; - -const client: AzionSQLClient = createClient({ token: 'your-api-token', debug: true }); - -const { data, error }: AzionDatabaseResponse = await client.createDatabase('my-new-db'); +const { data, error } = await createDatabase('my-new-database', { debug: true }); if (data) { console.log(`Database created with ID: ${data.id}`); -} - -const { data: allDatabases }: AzionDatabaseResponse = await client.getDatabases(); -if (allDatabases) { - console.log(`Retrieved ${allDatabases.count} databases`); +} else { + console.error('Failed to create database', error); } ``` -Read more in the [SQL README](./packages/sql/README.MD). - -### Purge - -The Purge library provides methods to purge URLs, Cache Keys, and Wildcard expressions from the Azion Edge cache. +More information on specific functionalities and usage can be found in the README file of each package (e.g., [Storage README](./packages/storage/README.md), [SQL README](./packages/sql/README.md), etc.). -#### Examples - -**JavaScript:** +This flexibility allows you to either manage everything through the client for simplicity or call specific functions from each package with more control over environment configurations. -```javascript -import { createClient } from 'azion/purge'; - -const client = createClient({ token: 'your-api-token', debug: true }); - -const purgeResult = await client.purgeURL(['http://example.com/image.jpg']); -if (purgeResult) { - console.log(`Purge successful: ${purgeResult.items}`); -} - -const cacheKeyResult = await client.purgeCacheKey(['my-cache-key-1', 'my-cache-key-2']); -if (cacheKeyResult) { - console.log(`Cache key purge successful: ${cacheKeyResult.items}`); -} -``` - -**TypeScript:** - -```typescript -import { createClient } from 'azion/purge'; -import { PurgeClient, Purge } from 'azion/purge/types'; - -const client: PurgeClient = createClient({ token: 'your-api-token', debug: true }); - -const purgeResult: Purge | null = await client.purgeURL(['http://example.com/image.jpg']); -if (purgeResult) { - console.log(`Purge successful: ${purgeResult.items}`); -} - -const cacheKeyResult: Purge | null = await client.purgeCacheKey(['my-cache-key-1', 'my-cache-key-2']); -if (cacheKeyResult) { - console.log(`Cache key purge successful: ${cacheKeyResult.items}`); -} -``` - -Read more in the [Purge README](./packages/purge/README.md). - -### Domains - -The Domains library provides methods to interact with Azion Edge Domains. - -#### Examples - -**JavaScript:** - -```javascript -import { createClient } from 'azion/domains'; - -const client = createClient({ token: 'your-token' }); - -const { data: allDomains } = await client.getDomains(); - -console.log(`Retrieved ${allDomains.count} domains`); -``` - -**TypeScript:** - -```typescript -import { createClient } from 'azion/domains'; -import type { AzionDomainsClient, AzionDomainsResponse, AzionDomains } from 'azion/domains'; - -const client: AzionDomainsClient = createClient({ token: 'your-token' }); - -const { data: allDomains }: AzionDomainsResponse = await client.getDomains(); - -console.log(`Retrieved ${allDomains.count} domains`); -``` - -Read more in the [Domains README](./packages/domains/README.md). - -## Utilities - -### Cookies - -The Cookies library provides methods to get and set cookies. - -#### Examples - -**JavaScript:** - -```javascript -import { getCookie, setCookie } from 'azion/cookies'; - -const myCookie = getCookie(request, 'my-cookie'); -setCookie(response, 'my-cookie', 'cookie-value', { maxAge: 3600 }); -``` - -**TypeScript:** - -```typescript -import { getCookie, setCookie } from 'azion/cookies'; -import { CookieOptions } from 'azion/cookies/types'; - -const myCookie: string | undefined = getCookie(request, 'my-cookie'); -const options: CookieOptions = { maxAge: 3600 }; -setCookie(response, 'my-cookie', 'cookie-value', options); -``` - -Read more in the [Cookies README](./packages/cookies/README.md). - -### Jwt +### Client -The Jwt library provides methods to sign, verify and decode tokens. +The Azion Client provides a unified interface to interact with all Azion services. #### Examples **JavaScript:** ```javascript -import { sign, verify, decode } from 'azion/jwt'; - -const key = 'your-key'; - -// sign -const inputPayload = { userId: 123, exp: Math.floor(Date.now() / 1000) + 3600 }; // 1 hour expiration -const token = await sign(inputPayload, key); -console.log(`created token: ${token}`); - -// verify -const verifyResult = await verify(token, key); -console.log(`verify result: ${JSON.stringify(verifyResult)}`); - -// decode -const { header, payload } = decode(token); -console.log(`decode result: ${JSON.stringify({ header, payload })}`); -``` - -**TypeScript:** - -```typescript -import { sign, verify, decode } from 'azion/jwt'; -import type { JWTPayload } from 'azion/jwt'; - -const key: string = 'your-key'; - -// sign -const inputPayload: JWTPayload = { userId: 123, exp: Math.floor(Date.now() / 1000) + 3600 }; // 1 hour expiration -const token: string = await sign(inputPayload, key); -console.log(`created token: ${token}`); - -// verify -const verifyResult: JWTPayload = await verify(token, key); -console.log(`verify result: ${JSON.stringify(verifyResult)}`); - -// decode -const { - header, - payload, -}: { - header: any; - payload: JWTPayload; -} = decode(token); -console.log(`decode result: ${JSON.stringify({ header, payload })}`); -``` - -Read more in the [Jwt README](./packages/jwt/README.md). +import { createClient } from 'azion'; -### WASM Image Processor +const client = createClient({ token: 'your-api-token', debug: true }); -The WASM Image Processor library provides methods to process images using WebAssembly. +// Storage +const { data: newBucket, error } = await client.storage.createBucket({ name: 'my-new-bucket', edge_access: 'public' }); +console.log(`Bucket created with name: ${newBucket.name}`); -#### Examples +const { data: allBuckets, error } = await client.storage.getBuckets(); +console.log(`Retrieved ${allBuckets.count} buckets`); -**JavaScript:** +// SQL +const { data: newDatabase, error } = await client.sql.createDatabase('my-new-db'); +console.log(`Database created with ID: ${newDatabase.id}`); -```javascript -import { loadImage } from 'azion/wasm-image-processor'; +const { data: allDatabases, error } = await client.sql.getDatabases(); +console.log(`Retrieved ${allDatabases.count} databases`); -const image = await loadImage('https://example.com/image.jpg'); -image.resize(0.5, 0.5); -const image = image.getImageResponse('jpeg'); -console.log(imageResponse); +// Purge +const { data: purgeResult, error } = await client.purge.purgeURL(['http://example.com/image.jpg']); +console.log(`Purge successful: ${purgeResult.items}`); ``` **TypeScript:** ```typescript -import { loadImage } from 'azion/wasm-image-processor'; -import { WasmImage } from 'azion/wasm-image-processor/types'; - -const image: WasmImage = await loadImage('https://example.com/image.jpg'); -image.resize(0.5, 0.5); -const imageResponse = image.getImageResponse('jpeg'); -console.log(imageResponse); -``` - -Read more in the [WASM Image Processor README](./packages/wasm-image-processor/README.md). - -### Utils - -The Utils package provides a set of utility functions that simplify common tasks when working with Azion edge functions. - -#### Available Functions - -- **`mountSPA(requestURL: RequestURL): Promise`** - Handles routing for Single-page Applications (SPA) by determining if the incoming request is for a static asset or an application route. It mounts the appropriate request URL for fetching the required resource. - -- **`mountMPA(requestURL: RequestURL): Promise`** - Handles routing for Multi-page Applications (MPA) by determining if the incoming request is for a static asset or an application route. It mounts the appropriate request URL for fetching the required resource. +import { createClient } from 'azion'; +import type { AzionClient } from 'azion/client'; +import type { AzionDatabaseResponse, AzionDatabaseQueryResponse, AzionDatabaseCollection } from 'azion/sql'; +import type { AzionStorageResponse, AzionBucket, AzionBucketCollection } from 'azion/storage'; +import type { AzionPurgeResponse, AzionPurge } from 'azion/purge'; -- **`parseRequest(event: FetchEvent): Promise`** - Parses and logs the details of an incoming request, extracting key information such as headers, cookies, body, and client data. It provides a structured object with these details for further processing or logging. +const client: AzionClient = createClient({ token: 'your-api-token', debug: true }); -#### Examples +// Storage +const { data: newBucket, error }: AzionStorageResponse = await client.createBucket({ + name: 'my-new-bucket', + edge_access: 'public', +}); +console.log(`Bucket created with name: ${newBucket.name}`); -**JavaScript:** +const { data: allBuckets, error }: AzionStorageResponse = await client.getBuckets(); +console.log(`Retrieved ${allBuckets.count} buckets`); -```javascript -import { mountSPA, mountMPA, parseRequest } from 'azion/utils'; - -// Handle SPA routing -const myApp1 = await mountSPA('https://example.com/'); -console.log(myApp1); -// Fetches: file:///index.html -// Response object representing the content of index.html - -// Handle MPA routing -const myApp2 = await mountMPA('https://example.com/about'); -console.log(myApp2); -// Fetches: file:///about/index.html -// Response object representing the content of about/index.html - -// Parse a request -const parsedRequest = await parseRequest(event); -console.log(parsedRequest); -``` +// SQL +const { data: newDatabase, error }: AzionDatabaseResponse = await client.sql.createDatabase('my-new-db'); +console.log(`Database created with ID: ${newDatabase.id}`); -**TypeScript:** +const { data: allDatabases, error }: AzionDatabaseResponse = await client.sql.getDatabases(); +console.log(`Retrieved ${allDatabases.count} databases`); -```typescript -import { mountSPA, mountMPA, parseRequest } from 'azion/utils'; -import { ParsedRequest } from 'azion/utils/types'; - -// Handle SPA routing -const myApp1: Response = await mountSPA('https://example.com/'); -console.log(myApp1); -// Fetches: file:///index.html -// Response object representing the content of index.html - -// Handle MPA routing -const myApp2: Response = await mountMPA('https://example.com/about'); -console.log(myApp2); -// Fetches: file:///about/index.html -// Response object representing the content of about/index.html - -// Parse a request -const parsedRequest: ParsedRequest = await parseRequest(event); -console.log(parsedRequest); +// Purge +const { data: purgeResult, error }: AzionPurgeResponse = await client.purge.purgeURL([ + 'http://example.com/image.jpg', +]); +console.log(`Purge successful: ${purgeResult.items}`); ``` -Read more in the [Utils README](./packages/utils/README.md). - ## Types The Types package provides global TypeScript types that are used across Azion platform, ensuring consistency and reducing redundancy throughout the codebase. @@ -480,6 +194,7 @@ This is the first example using JSDoc to provide type information: ```javascript /** @type {import('azion').AzionConfig} */ const config = { + build: {}, domain: {}, origin: [], cache: [], @@ -496,6 +211,7 @@ This is the second example using the `defineConfig` function to enforce types an import { defineConfig } from 'azion'; const config = defineConfig({ + build: {}, domain: {}, origin: [], cache: [], diff --git a/packages/applications/README.md b/packages/applications/README.md new file mode 100644 index 0000000..e69de29 diff --git a/packages/client/README.MD b/packages/client/README.MD index 1811645..91768d4 100644 --- a/packages/client/README.MD +++ b/packages/client/README.MD @@ -90,7 +90,13 @@ if (deletedBucket) { ```typescript import { createClient } from 'azion'; -import { AzionClient, AzionStorageResponse, AzionBucket, AzionDeletedBucket, AzionBuckets } from 'azion/storage'; +import { + AzionClient, + AzionStorageResponse, + AzionBucket, + AzionDeletedBucket, + AzionBucketCollection, +} from 'azion/storage'; const client: AzionClient = createClient({ token: 'your-api-token', debug: true }); @@ -102,7 +108,7 @@ if (newBucket) { console.log(`Bucket created with name: ${newBucket.name}`); } -const { data: allBuckets }: AzionStorageResponse = await client.storage.getBuckets(); +const { data: allBuckets }: AzionStorageResponse = await client.storage.getBuckets(); if (allBuckets) { console.log(`Retrieved ${allBuckets.count} buckets`); } diff --git a/packages/storage/README.md b/packages/storage/README.md index 5c109e0..48ecc1b 100644 --- a/packages/storage/README.md +++ b/packages/storage/README.md @@ -169,9 +169,9 @@ if (buckets) { **TypeScript:** ```typescript -import { getBuckets, AzionStorageResponse, AzionBuckets } from 'azion/storage'; +import { getBuckets, AzionStorageResponse, AzionBucketCollection } from 'azion/storage'; -const { data: buckets, error }: AzionStorageResponse = await getBuckets({ +const { data: buckets, error }: AzionStorageResponse = await getBuckets({ params: { page: 1, page_size: 10 }, }); if (buckets) { @@ -451,7 +451,7 @@ import { AzionStorageResponse, AzionBucket, AzionBucketObject, - AzionBuckets, + AzionBucketCollection, } from 'azion/storage'; const client: StorageClient = createClient({ token: 'your-api-token', debug: true }); @@ -464,7 +464,7 @@ if (data) { console.log(`Bucket created with name: ${data.name}`); } -const { data: allBuckets }: AzionStorageResponse = await client.getBuckets(); +const { data: allBuckets }: AzionStorageResponse = await client.getBuckets(); if (allBuckets) { console.log(`Retrieved ${allBuckets.count} buckets`); } @@ -530,7 +530,7 @@ Retrieves a list of buckets with optional filtering and pagination. **Returns:** -- `Promise>` - Array of bucket objects or error. +- `Promise>` - Array of bucket objects or error. ### `getBucket` @@ -655,7 +655,7 @@ Configuration options for the Storage client. An object with methods to interact with Storage. -- `getBuckets: (options?: BucketCollectionOptions) => Promise>` +- `getBuckets: (options?: BucketCollectionOptions) => Promise>` - `createBucket: (name: string, edge_access: string) => Promise>` - `updateBucket: (name: string, edge_access: string) => Promise>` - `deleteBucket: (name: string) => Promise>` diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts index 3d74f05..f296eb5 100644 --- a/packages/storage/src/index.ts +++ b/packages/storage/src/index.ts @@ -11,10 +11,10 @@ import { } from './services/api/index'; import { AzionBucket, + AzionBucketCollection, AzionBucketCollectionParams, AzionBucketObject, AzionBucketObjects, - AzionBuckets, AzionClientOptions, AzionDeletedBucket, AzionDeletedBucketObject, @@ -130,13 +130,13 @@ export const deleteBucketMethod = async ( * @param {string} token - Authentication token for Azion API. * @param {AzionBucketCollectionParams} [params] - Optional parameters for filtering and pagination. * @param {AzionClientOptions} [options] - Client options including debug mode. - * @returns {Promise>} Array of bucket objects or error message. + * @returns {Promise>} Array of bucket objects or error message. */ export const getBucketsMethod = async ( token: string, params?: AzionBucketCollectionParams, options?: AzionClientOptions, -): Promise> => { +): Promise> => { const apiResponse = await getBuckets(resolveToken(token), params, resolveDebug(options?.debug)); if (apiResponse?.results && apiResponse.results.length > 0) { const buckets = apiResponse.results?.map((bucket) => ({ @@ -648,7 +648,7 @@ const getBucketsWrapper = ({ }: { params?: AzionBucketCollectionParams; options?: AzionClientOptions; -}): Promise> => +}): Promise> => getBucketsMethod(resolveToken(), params, { ...options, debug: resolveDebug(options?.debug) }); /** @@ -886,7 +886,9 @@ const client: CreateAzionStorageClient = ( const debugValue = resolveDebug(config?.options?.debug); const client: AzionStorageClient = { - getBuckets: (params?: { params?: AzionBucketCollectionParams }): Promise> => + getBuckets: (params?: { + params?: AzionBucketCollectionParams; + }): Promise> => getBucketsMethod(tokenValue, params?.params, { ...config, debug: debugValue }), createBucket: ({ name, diff --git a/packages/storage/src/types.ts b/packages/storage/src/types.ts index fbe4a81..3af26d5 100644 --- a/packages/storage/src/types.ts +++ b/packages/storage/src/types.ts @@ -129,7 +129,7 @@ export interface AzionDeletedBucket { state?: 'executed' | 'executed-runtime' | 'pending'; } -export interface AzionBuckets { +export interface AzionBucketCollection { buckets: AzionBucket[]; count: number; } @@ -139,9 +139,11 @@ export interface AzionStorageClient { * Retrieves a list of buckets with optional filtering and pagination. * @param {Object} params - Parameters for retrieving buckets. * @param {AzionBucketCollectionParams} [params.params] - Optional parameters for filtering and pagination. - * @returns {Promise>} Array of buckets or error message. + * @returns {Promise>} Array of buckets or error message. */ - getBuckets: (params?: { params?: AzionBucketCollectionParams }) => Promise>; + getBuckets: (params?: { + params?: AzionBucketCollectionParams; + }) => Promise>; /** * Creates a new bucket. * @param {Object} params - Parameters for creating a bucket. From 31c30ae857cd4b2184bbeae1dd601fcc9e66d3e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 16 Sep 2024 15:42:15 -0300 Subject: [PATCH 36/45] refactor: adjust code to meet standards (#40) * refactor: bucketcollection type * refactor: domain collection type * docs: fix uppercase * refactor: product name * docs: fix link --- README.md | 19 ++++++++++--------- packages/applications/src/index.ts | 6 +++--- packages/applications/src/types.ts | 20 ++++++++++---------- packages/client/README.MD | 10 ++++++++-- packages/client/src/index.ts | 11 ++++++----- packages/client/src/types.ts | 18 +++++++++--------- packages/domains/README.md | 10 +++++----- packages/domains/src/index.ts | 6 +++--- packages/domains/src/types.ts | 10 +++++----- 9 files changed, 59 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index d8cd10a..f6415c1 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,16 @@ These libraries are designed to be versatile and can be used both within and out - [Installation](#installation) - [Products](#products) - [Client](#client) - - [Storage](./packages/storage/README.MD) - - [SQL](./packages/sql/README.MD) - - [Purge](./packages/purge/README.MD) - - [Domains](./packages/domains/README.MD) - - [Applications](./packages/applications/README.MD) + - [Storage](./packages/storage/README.md) + - [SQL](./packages/sql/README.md) + - [Purge](./packages/purge/README.md) + - [Domains](./packages/domains/README.md) + - [Applications](./packages/applications/README.md) - Utilities - - [Cookies](./packages/cookies/README.MD) - - [Jwt](./packages/jwt/README.MD) - - [WASM Image Processor](./packages/wasm-image-processor/README.MD) - - [Utils](./packages/utils/README.MD) + - [Cookies](./packages/cookies/README.md) + - [Jwt](./packages/jwt/README.md) + - [WASM Image Processor](./packages/wasm-image-processor/README.md) + - [Utils](./packages/utils/README.md) - [Types](#types) - [AzionConfig](#config) - [Contributing](#contributing) @@ -227,3 +227,4 @@ Read more in the [AzionConfig README](./packages/config/README.md). ## Contributing Feel free to submit issues or pull requests to improve the functionality or documentation. +AzionDomainCollectionAzionDomainCollection diff --git a/packages/applications/src/index.ts b/packages/applications/src/index.ts index 52d7141..755e18b 100644 --- a/packages/applications/src/index.ts +++ b/packages/applications/src/index.ts @@ -57,8 +57,8 @@ import { } from './rules-engine/index'; import type { - AzionApplicationClient, AzionApplicationCollectionOptions, + AzionApplicationsClient, AzionClientOptions, CreateAzionApplicationClient, } from './types'; @@ -69,11 +69,11 @@ import { resolveDebug, resolveToken } from './utils'; const createAzionApplicationClient: CreateAzionApplicationClient = ( config?: Partial<{ token: string; options?: AzionClientOptions }>, -): AzionApplicationClient => { +): AzionApplicationsClient => { const tokenValue = resolveToken(config?.token); const debugValue = resolveDebug(config?.options?.debug); - const client: AzionApplicationClient = { + const client: AzionApplicationsClient = { createApplication: async ({ data }: { data: ApiCreateApplicationPayload; options?: AzionClientOptions }) => createApplicationMethod(tokenValue, data, { ...config, debug: debugValue }), deleteApplication: async ({ applicationId, options }: { applicationId: number; options?: AzionClientOptions }) => diff --git a/packages/applications/src/types.ts b/packages/applications/src/types.ts index f080193..ff6f919 100644 --- a/packages/applications/src/types.ts +++ b/packages/applications/src/types.ts @@ -981,14 +981,14 @@ export interface AzionApplication extends ApiApplication { /** * Interface for the Azion Application Client, providing methods to interact with Azion Edge Applications. * - * @interface AzionApplicationClient + * @interface AzionApplicationsClient */ -export interface AzionApplicationClient { +export interface AzionApplicationsClient { /** * Creates a new Azion Edge Application. * * @function - * @name AzionApplicationClient.createApplication + * @name AzionApplicationsClient.createApplication * @param {Object} params - The parameters for creating an application. * @param {ApiCreateApplicationPayload} params.data - The data for the new application. @@ -1019,7 +1019,7 @@ export interface AzionApplicationClient { * Retrieves a specific Azion Edge Application. * * @function - * @name AzionApplicationClient.getApplication + * @name AzionApplicationsClient.getApplication * @param {Object} params - The parameters for retrieving an application. * @param {number} params.applicationId - The ID of the application to retrieve. @@ -1045,7 +1045,7 @@ export interface AzionApplicationClient { * Retrieves a list of Azion Edge Applications. * * @function - * @name AzionApplicationClient.getApplications + * @name AzionApplicationsClient.getApplications * @param {Object} params - The parameters for retrieving applications. * @param {AzionApplicationCollectionOptions} [params.params] - Optional parameters for filtering and pagination. @@ -1071,7 +1071,7 @@ export interface AzionApplicationClient { * Updates an existing Azion Edge Application. * * @function - * @name AzionApplicationClient.putApplication + * @name AzionApplicationsClient.putApplication * @param {Object} params - The parameters for updating an application. * @param {number} params.applicationId - The ID of the application to update. * @param {ApiUpdateApplicationPayload} params.data - The updated data for the application. @@ -1103,7 +1103,7 @@ export interface AzionApplicationClient { * Deletes an Azion Edge Application. * * @function - * @name AzionApplicationClient.deleteApplication + * @name AzionApplicationsClient.deleteApplication * @param {Object} params - The parameters for deleting an application. * @param {number} params.applicationId - The ID of the application to delete. @@ -1129,7 +1129,7 @@ export interface AzionApplicationClient { * Partially updates an existing Azion Edge Application. * * @function - * @name AzionApplicationClient.patchApplication + * @name AzionApplicationsClient.patchApplication * @param {Object} params - The parameters for partially updating an application. * @param {number} params.applicationId - The ID of the application to update. * @param {Partial} params.data - The partial data for updating the application. @@ -1163,7 +1163,7 @@ export interface AzionApplicationClient { * the client will attempt to use the AZION_TOKEN environment variable. * @param {AzionClientOptions} [config.options] - Additional client options. * - * @returns {AzionApplicationClient} An instance of the Azion Application Client. + * @returns {AzionApplicationsClient} An instance of the Azion Application Client. * * @example * // Create an application client with a token and debug mode enabled @@ -1178,4 +1178,4 @@ export interface AzionApplicationClient { */ export type CreateAzionApplicationClient = ( config?: Partial<{ token: string; options?: AzionClientOptions }>, -) => AzionApplicationClient; +) => AzionApplicationsClient; diff --git a/packages/client/README.MD b/packages/client/README.MD index 91768d4..6231487 100644 --- a/packages/client/README.MD +++ b/packages/client/README.MD @@ -258,7 +258,13 @@ if (deletedDomain) { ```typescript import { createClient } from 'azion'; -import { AzionClient, AzionDomainsResponse, AzionDomain, AzionDeletedDomain, AzionDomains } from 'azion/domains'; +import { + AzionClient, + AzionDomainsResponse, + AzionDomain, + AzionDeletedDomain, + AzionDomainCollection, +} from 'azion/domains'; const client: AzionClient = createClient({ token: 'your-api-token', debug: true }); @@ -271,7 +277,7 @@ if (newDomain) { console.log(`Domain created with name: ${newDomain.name}`); } -const { data: allDomains }: AzionDomainsResponse = await client.domains.getDomains(); +const { data: allDomains }: AzionDomainsResponse = await client.domains.getDomains(); if (allDomains) { console.log(`Retrieved ${allDomains.count} domains`); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 04cd015..9bd93f1 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,4 +1,4 @@ -import createAzionApplicationClient, { AzionApplicationClient } from 'azion/applications'; +import createAzionApplicationClient, { AzionApplicationsClient } from 'azion/applications'; import { defineConfig } from 'azion/config'; import createDomainsClient, { AzionDomainsClient } from 'azion/domains'; import createPurgeClient, { AzionPurgeClient } from 'azion/purge'; @@ -51,9 +51,9 @@ function createClient({ token, options }: AzionClientConfig = {}): AzionClient { /** * Azion Client object containing Storage, SQL, and Purge clients. * Edge Application client with methods to interact with Azion Edge Applications. - * @type {AzionApplicationClient} + * @type {AzionApplicationsClient} */ - const applicationClient: AzionApplicationClient = createAzionApplicationClient({ token, options }); + const applicationClient: AzionApplicationsClient = createAzionApplicationClient({ token, options }); /** * Azion Client object containing Storage, SQL, Purge, and Edge Application clients. @@ -64,14 +64,15 @@ function createClient({ token, options }: AzionClientConfig = {}): AzionClient { * @property {AzionStorageClient} storage - Client for Azion Edge Storage operations. * @property {AzionSQLClient} sql - Client for Azion Edge SQL database operations. * @property {AzionPurgeClient} purge - Client for Azion Edge Purge operations. - * @property {AzionApplicationClient} application - Client for Azion Edge Application operations. + * @property {AzionDomainsClient} applications - Client for Azion Edge Domains operations. + * @property {AzionApplicationsClient} applications - Client for Azion Edge Application operations. */ const client: AzionClient = { storage: storageClient, sql: sqlClient, purge: purgeClient, domains: domainsClient, - application: applicationClient, + applications: applicationClient, }; return client; diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index 49c0aa5..1d2b63e 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -1,7 +1,7 @@ /* eslint-disable no-unused-vars */ import { AzionDomainsClient } from 'azion/domains'; -import { AzionApplicationClient } from '../../applications/src/types'; +import { AzionApplicationsClient } from '../../applications/src/types'; import { AzionPurgeClient } from '../../purge/src/types'; import { AzionClientOptions, AzionSQLClient } from '../../sql/src/types'; import { AzionStorageClient } from '../../storage/src/types'; @@ -14,7 +14,7 @@ import { AzionStorageClient } from '../../storage/src/types'; * @property {AzionStorageClient} storage - Client for Azion Edge Storage operations. * @property {AzionSQLClient} sql - Client for Azion Edge SQL database operations. * @property {AzionPurgeClient} purge - Client for Azion Edge Purge operations. - * @property {AzionApplicationClient} - Client for Azion Edge Application operations. + * @property {AzionApplicationsClient} - Client for Azion Edge Application operations. */ export interface AzionClient { /** @@ -132,11 +132,11 @@ export interface AzionClient { /** * Edge Application client with methods to interact with Azion Edge Applications. * - * @type {AzionApplicationClient} + * @type {AzionApplicationsClient} * * @example * // Create a new Edge Application - * const { data: newApp } = await client.application.createApplication({ + * const { data: newApp } = await client.applications.createApplication({ * data: { * name: 'My New App', * delivery_protocol: 'http', @@ -147,13 +147,13 @@ export interface AzionClient { * * @example * // Get all Edge Applications - * const { data: allApps } = await client.application.getApplications({ + * const { data: allApps } = await client.applications.getApplications({ * params: { page: 1, page_size: 20, sort: 'name', order_by: 'asc' } * }); * * @example * // Get a specific Edge Application and perform operations - * const { data: app } = await client.application.getApplication({ applicationId: 123 }); + * const { data: app } = await client.applications.getApplication({ applicationId: 123 }); * if (app) { * // Create a new cache setting * const { data: newCacheSetting } = await app.cache.createCacheSetting({ @@ -186,16 +186,16 @@ export interface AzionClient { * * @example * // Update an Edge Application - * const { data: updatedApp } = await client.application.putApplication({ + * const { data: updatedApp } = await client.applications.putApplication({ * applicationId: 123, * data: { name: 'Updated App Name', delivery_protocol: 'https' } * }); * * @example * // Delete an Edge Application - * const { data: deletedApp } = await client.application.deleteApplication({ applicationId: 123 }); + * const { data: deletedApp } = await client.applications.deleteApplication({ applicationId: 123 }); */ - application: AzionApplicationClient; + applications: AzionApplicationsClient; } /** diff --git a/packages/domains/README.md b/packages/domains/README.md index e2a0453..a4a2e08 100644 --- a/packages/domains/README.md +++ b/packages/domains/README.md @@ -28,7 +28,7 @@ Azion Edge Domains provides a simple interface to interact with the Azion Edge D - [`ClientConfig`](#clientconfig) - [`AzionDomainsResponse`](#aziondomainsresponset) - [`AzionDomain`](#aziondomain) - - [`AzionDomains`](#aziondomains) + - [`AzionDomainCollection`](#aziondomaincollection) - [`AzionDeleteDomain`](#aziondeletedomain) - [Contributing](#contributing) @@ -132,9 +132,9 @@ if (domains) { ```typescript import { getDomains } from 'azion/domains'; -import type { AzionDomains, AzionDomainsResponse } from 'azion/domains'; +import type { AzionDomainCollection, AzionDomainsResponse } from 'azion/domains'; -const { data: domains, error }: AzionDomainsResponse = await getDomains(); +const { data: domains, error }: AzionDomainsResponse = await getDomains(); if (domains) { console.log(`Found ${domains.count} domains`); @@ -303,7 +303,7 @@ Lists all domains. **Returns:** -- `Promise>` - An array of domain objects or error failed. +- `Promise>` - An array of domain objects or error failed. ### `getDomain` @@ -390,7 +390,7 @@ Configuration options for the Azion Domains client. - `mtls.trustedCaCertificateId: number` - Trusted CA certificate ID. - `mtls.crlList: number[]` - List of CRL IDs. -### `AzionDomains` +### `AzionDomainCollection` - `state: 'pending' | 'executed' | 'failed'` - State of the domain list. - `pages: number` - Number of pages. diff --git a/packages/domains/src/index.ts b/packages/domains/src/index.ts index 0d9a16b..4f78a25 100644 --- a/packages/domains/src/index.ts +++ b/packages/domains/src/index.ts @@ -4,7 +4,7 @@ import { AzionCreateDomain, AzionDeletedDomain, AzionDomain, - AzionDomains, + AzionDomainCollection, AzionDomainsClient, AzionDomainsCreateClient, AzionDomainsResponse, @@ -65,7 +65,7 @@ const getDomainsMethod = async ( token: string, queryParams?: { order_by?: 'id' | 'name'; page?: number; pageSize?: number; sort?: 'asc' | 'desc' }, options?: AzionClientOptions, -): Promise> => { +): Promise> => { const apiResponse = await getDomains(resolveToken(token), options, queryParams); if (apiResponse.results) { return { @@ -254,7 +254,7 @@ const createDomainWrapper = async ( const getDomainsWrapper = async ( options?: AzionClientOptions, queryParams?: { orderBy?: 'id' | 'name'; page?: number; pageSize?: number; sort?: 'asc' | 'desc' }, -): Promise> => { +): Promise> => { return getDomainsMethod(resolveToken(), queryParams, options); }; diff --git a/packages/domains/src/types.ts b/packages/domains/src/types.ts index d86fc76..4ce9cdd 100644 --- a/packages/domains/src/types.ts +++ b/packages/domains/src/types.ts @@ -56,7 +56,7 @@ export type AzionDomain = { }; /** - * AzionDomains is a type that represents the domains object in the Azion API. + * AzionDomainCollection is a type that represents the domains object in the Azion API. * @param state The state of the response. * @param count The count of the domains. * @param pages The number of pages. @@ -106,14 +106,14 @@ export type AzionClientOptions = { }; /** - * AzionDomains is a type that represents the domains object in the Azion API. + * AzionDomainCollection is a type that represents the domains object in the Azion API. * @param state The state of the response. * @param count The count of the domains. * @param pages The number of pages. * @param results The list of domains. * @returns An object with the domains data. */ -export type AzionDomains = { +export type AzionDomainCollection = { state: ResponseState; count: number; pages: number; @@ -160,7 +160,7 @@ export interface AzionDomainsClient { * getDomains is a function that gets the domains in the Azion API. * @param {AzionClientOptions} options The options of the client. * @param {{ orderBy?: 'id' | 'name'; page?: number; pageSize?: number; sort?: 'asc' | 'desc' }} queryParams The query parameters of the request. - * @returns {Promise>} The response of the API. + * @returns {Promise>} The response of the API. * @example * const { data, error } = await client.getDomains(); * if (error) { @@ -172,7 +172,7 @@ export interface AzionDomainsClient { getDomains: ( options?: AzionClientOptions, queryParams?: { orderBy?: 'id' | 'name'; page?: number; pageSize?: number; sort?: 'asc' | 'desc' }, - ) => Promise>; + ) => Promise>; /** * getDomain is a function that gets a domain in the Azion API. * @param {number} domainId The id of the domain. From 819b063b49233e49c2e5014d0caf5965eea4da10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 16 Sep 2024 15:50:49 -0300 Subject: [PATCH 37/45] docs: index correction --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6415c1..a05e667 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ These libraries are designed to be versatile and can be used both within and out ## Table of Contents - [Installation](#installation) -- [Products](#products) +- [Usage](#usage) - [Client](#client) - [Storage](./packages/storage/README.md) - [SQL](./packages/sql/README.md) @@ -37,7 +37,7 @@ or yarn add azion ``` -## Products +## Usage ### Using the Client vs. Independent Package Functions From bc69de10ff9d7f7f16d5fb5224348df10644603e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 16 Sep 2024 15:53:38 -0300 Subject: [PATCH 38/45] docs: fix README extension --- packages/wasm-image-processor/{README.MD => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/wasm-image-processor/{README.MD => README.md} (100%) diff --git a/packages/wasm-image-processor/README.MD b/packages/wasm-image-processor/README.md similarity index 100% rename from packages/wasm-image-processor/README.MD rename to packages/wasm-image-processor/README.md From cf7ea212a387e28e76b191f539d130293a740881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 16 Sep 2024 15:56:47 -0300 Subject: [PATCH 39/45] docs: cookies --- packages/cookies/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cookies/README.md b/packages/cookies/README.md index 992db64..7bb7eca 100644 --- a/packages/cookies/README.md +++ b/packages/cookies/README.md @@ -46,7 +46,7 @@ console.log(myCookie); // Outputs the value of 'my-cookie' **TypeScript:** ```typescript -import { getCookie, CookiePrefix } from 'azion/cookies'; +import { getCookie } from 'azion/cookies'; const myCookie: string | undefined = getCookie(request, 'my-cookie'); const secureCookie: string | undefined = getCookie(request, 'my-cookie', 'secure'); @@ -68,7 +68,7 @@ const res = setCookie(response, 'my-cookie', 'cookie-value', { maxAge: 3600 }); ```typescript import { setCookie } from 'azion/cookies'; -import { CookieOptions } from 'azion/cookies/types'; +import type { CookieOptions } from 'azion/cookies'; const options: CookieOptions = { maxAge: 3600 }; const res = setCookie(response, 'my-cookie', 'cookie-value', options); From 935c8f9c9ac11584899e9b4e36fb77cba0371447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 16 Sep 2024 15:58:56 -0300 Subject: [PATCH 40/45] docs: wasm --- packages/wasm-image-processor/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/wasm-image-processor/README.md b/packages/wasm-image-processor/README.md index aa539f1..2b8e398 100644 --- a/packages/wasm-image-processor/README.md +++ b/packages/wasm-image-processor/README.md @@ -50,7 +50,8 @@ const image = await loadImage('https://example.com/image.jpg'); **TypeScript:** ```typescript -import { loadImage, WasmImage } from 'azion/wasm-image-processor'; +import { loadImage } from 'azion/wasm-image-processor'; +import type { WasmImage } from 'azion/wasm-image-processor'; const image: WasmImage = await loadImage('https://example.com/image.jpg'); ``` @@ -69,7 +70,8 @@ const resizedImage = image.resize(0.5, 0.5); **TypeScript:** ```typescript -import { loadImage, WasmImage } from 'azion/wasm-image-processor'; +import { loadImage } from 'azion/wasm-image-processor'; +import type { WasmImage } from 'azion/wasm-image-processor'; const image: WasmImage = await loadImage('https://example.com/image.jpg'); const resizedImage: WasmImage = image.resize(0.5, 0.5); @@ -90,7 +92,8 @@ console.log(imageResponse); **TypeScript:** ```typescript -import { loadImage, WasmImage, SupportedImageFormat } from 'azion/wasm-image-processor'; +import { loadImage } from 'azion/wasm-image-processor'; +import type { WasmImage, SupportedImageFormat } from 'azion/wasm-image-processor'; const image: WasmImage = await loadImage('https://example.com/image.jpg'); const imageResponse: Response = image.getImageResponse('jpeg' as SupportedImageFormat); @@ -111,7 +114,8 @@ image.clean(); **TypeScript:** ```typescript -import { loadImage, WasmImage } from 'azion/wasm-image-processor'; +import { loadImage } from 'azion/wasm-image-processor'; +import type { WasmImage, SupportedImageFormat } from 'azion/wasm-image-processor'; const image: WasmImage = await loadImage('https://example.com/image.jpg'); image.clean(); @@ -134,7 +138,8 @@ clean(resizedImage); **TypeScript:** ```typescript -import { loadImage, resize, getImageResponse, clean, PhotonImage } from 'azion/wasm-image-processor'; +import { loadImage, resize, getImageResponse, clean } from 'azion/wasm-image-processor'; +import type { WasmImage, PhotonImage } from 'azion/wasm-image-processor'; const image: WasmImage = await loadImage('https://example.com/image.jpg'); const resizedImage: PhotonImage = resize(image.image, 0.5, 0.5); From 8712a12a9614657adb79b0598ea14199337251b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 16 Sep 2024 16:01:25 -0300 Subject: [PATCH 41/45] docs: jwt --- packages/jwt/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/jwt/README.md b/packages/jwt/README.md index 477e11f..b73e4f3 100644 --- a/packages/jwt/README.md +++ b/packages/jwt/README.md @@ -57,7 +57,8 @@ sign(payload, privateKey).then((token) => console.log(token)); // Outputs the si **TypeScript:** ```typescript -import { sign, JWTPayload } from 'azion/jwt'; +import { sign } from 'azion/jwt'; +import type { JWTPayload } from 'azion/jwt'; const privateKey: string = 'your-private-key'; const payload: JWTPayload = { userId: 123, exp: Math.floor(Date.now() / 1000) + 3600 }; // 1 hour expiration @@ -81,7 +82,8 @@ verify(token, publicKey) **TypeScript:** ```typescript -import { verify, JWTPayload } from 'azion/jwt'; +import { verify } from 'azion/jwt'; +import type { JWTPayload } from 'azion/jwt'; const publicKey: string = 'your-public-key'; const token: string = 'your-jwt-token'; @@ -105,7 +107,8 @@ console.log(header, payload); // Outputs the decoded header and payload **TypeScript:** ```typescript -import { decode, TokenHeader, JWTPayload } from 'azion/jwt'; +import { decode } from 'azion/jwt'; +import type { JWTPayload, TokenHeader } from 'azion/jwt'; const token: string = 'your-jwt-token'; const { header, payload }: { header: TokenHeader; payload: JWTPayload } = decode(token); From 6c3ff7aaa76ce31aef6f29bb2012f2f78f1fcf1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 16 Sep 2024 16:02:59 -0300 Subject: [PATCH 42/45] docs: purge --- packages/purge/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/purge/README.md b/packages/purge/README.md index bbfb65d..a35e349 100644 --- a/packages/purge/README.md +++ b/packages/purge/README.md @@ -93,7 +93,8 @@ if (response) { **TypeScript:** ```typescript -import { purgeURL, AzionPurgeResponse, AzionPurge } from 'azion/purge'; +import { purgeURL } from 'azion/purge'; +import type { AzionPurgeResponse, AzionPurge } from 'azion/purge'; const url: string[] = ['http://www.domain.com/path/image.jpg']; const { data: response, error }: AzionPurgeResponse = await purgeURL(url, { debug: true }); @@ -123,7 +124,8 @@ if (response) { **TypeScript:** ```typescript -import { purgeCacheKey, AzionPurge, AzionPurgeResponse } from 'azion/purge'; +import { purgeCacheKey } from 'azion/purge'; +import type { AzionPurgeResponse, AzionPurge } from 'azion/purge'; const cacheKey: string[] = ['http://www.domain.com/path/image.jpg']; const { data: response, error }: AzionPurgeResponse = await purgeCacheKey(cacheKey, { debug: true }); @@ -153,7 +155,8 @@ if (response) { **TypeScript:** ```typescript -import { purgeWildCard, AzionPurge, AzionPurgeResponse } from 'azion/purge'; +import { purgeWildCard } from 'azion/purge'; +import type { AzionPurgeResponse, AzionPurge } from 'azion/purge'; const wildcard: string[] = ['http://www.domain.com/path/image.jpg*']; const { data: response, error }: AzionPurgeResponse = await purgeWildCard(wildcard, { debug: true }); @@ -198,7 +201,8 @@ if (purgeWildCardResponse) { **TypeScript:** ```typescript -import { createClient, AzionPurge, AzionPurgeClient, AzionPurgeResponse } from 'azion/purge'; +import { createClient, } from 'azion/purge'; +import type { AzionPurgeClient, AzionPurgeResponse, AzionPurge } from 'azion/purge'; const client: AzionPurgeClient = createClient({ token: 'your-api-token', options: { debug: true } }); From 684af444b05a6191d57767a94824ca657d134245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Narciso?= Date: Mon, 16 Sep 2024 16:04:03 -0300 Subject: [PATCH 43/45] docs: utils --- packages/utils/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/README.md b/packages/utils/README.md index 31af70a..41268fe 100644 --- a/packages/utils/README.md +++ b/packages/utils/README.md @@ -153,7 +153,7 @@ console.log(parsedRequest); ```typescript import { parseRequest } from 'azion/utils'; -import { ParsedRequest } from 'azion/utils/types'; +import type { ParsedRequest } from 'azion/utils'; const parsedRequest: ParsedRequest = await parseRequest(event); console.log(parsedRequest); From 9a6afc0233f356e0a20f60bcf39ed03347689fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Filho?= Date: Tue, 17 Sep 2024 11:41:25 -0300 Subject: [PATCH 44/45] feat: process config in azion/config (#41) * feat: add types build config * feat: adding the generate manifest to azion/config The generate manifest has been moved from the azion bundler to the azion/config package. * chore: update processConfig instead of generateManifest in azion/config * docs: update docs in azion/config --- package-lock.json | 149 +- packages/client/src/index.ts | 4 +- packages/client/tsconfig.json | 1 + packages/config/.gitignore | 2 + packages/config/README.md | 53 +- packages/config/jest.config.js | 10 + packages/config/package.json | 13 +- packages/config/src/index.ts | 25 +- .../helpers/azion.config.example.ts | 328 +++ .../src/processConfig/helpers/behaviors.ts | 211 ++ .../helpers/convertLegacyConfig.ts | 68 + .../src/processConfig/helpers/schema.ts | 845 ++++++ .../config/src/processConfig/index.test.ts | 2276 +++++++++++++++++ packages/config/src/processConfig/index.ts | 86 + .../buildProcessConfigStrategy.ts | 20 + .../cacheProcessConfigStrategy.ts | 70 + .../domainProcessConfigStrategy.ts | 57 + .../originProcessConfigStrategy.ts | 78 + .../purgeProcessConfigStrategy.ts | 44 + .../rulesProcessConfigStrategy.ts | 97 + .../strategy/processConfigContext.ts | 27 + .../strategy/processConfigStrategy.ts | 16 + packages/config/src/types.ts | 31 +- packages/config/tsconfig.json | 5 +- 24 files changed, 4495 insertions(+), 21 deletions(-) create mode 100644 packages/config/.gitignore create mode 100644 packages/config/jest.config.js create mode 100644 packages/config/src/processConfig/helpers/azion.config.example.ts create mode 100644 packages/config/src/processConfig/helpers/behaviors.ts create mode 100644 packages/config/src/processConfig/helpers/convertLegacyConfig.ts create mode 100644 packages/config/src/processConfig/helpers/schema.ts create mode 100644 packages/config/src/processConfig/index.test.ts create mode 100644 packages/config/src/processConfig/index.ts create mode 100644 packages/config/src/processConfig/strategy/implementations/buildProcessConfigStrategy.ts create mode 100644 packages/config/src/processConfig/strategy/implementations/cacheProcessConfigStrategy.ts create mode 100644 packages/config/src/processConfig/strategy/implementations/domainProcessConfigStrategy.ts create mode 100644 packages/config/src/processConfig/strategy/implementations/originProcessConfigStrategy.ts create mode 100644 packages/config/src/processConfig/strategy/implementations/purgeProcessConfigStrategy.ts create mode 100644 packages/config/src/processConfig/strategy/implementations/rulesProcessConfigStrategy.ts create mode 100644 packages/config/src/processConfig/strategy/processConfigContext.ts create mode 100644 packages/config/src/processConfig/strategy/processConfigStrategy.ts diff --git a/package-lock.json b/package-lock.json index 26def44..4a92985 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,23 +1,24 @@ { "name": "azion", - "version": "1.7.0-stage.1", + "version": "1.7.0-stage.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "azion", - "version": "1.7.0-stage.1", + "version": "1.7.0-stage.11", "hasInstallScript": true, "license": "MIT", "workspaces": [ "packages/*" ], "dependencies": { + "ajv-keywords": "^5.1.0", "chalk": "^5.3.0", "progress": "^2.0.3" }, "bin": { - "azion": "cli/bin/azion" + "azion": "bin/azion" }, "devDependencies": { "@commitlint/cli": "^18.4.1", @@ -594,6 +595,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", + "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", @@ -2202,6 +2214,10 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lib/application": { + "resolved": "packages/applications", + "link": true + }, "node_modules/@lib/client": { "resolved": "packages/client", "link": true @@ -3981,7 +3997,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3993,6 +4008,25 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", + "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", + "peerDependencies": { + "ajv": "^8.0.1" + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, "node_modules/ansi-escapes": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", @@ -4628,6 +4662,18 @@ "dot-prop": "^5.1.0" } }, + "node_modules/complex.js": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz", + "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4951,6 +4997,11 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -5304,6 +5355,11 @@ "node": ">=6" } }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -5608,8 +5664,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -5654,8 +5709,7 @@ "node_modules/fast-uri": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", - "dev": true + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" }, "node_modules/fastq": { "version": "1.17.1", @@ -5836,6 +5890,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -6653,6 +6719,11 @@ "node": ">= 0.6.0" } }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -7582,8 +7653,7 @@ "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -7971,6 +8041,28 @@ "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" } }, + "node_modules/mathjs": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-13.1.1.tgz", + "integrity": "sha512-duaSAy7m4F+QtP1Dyv8MX2XuxcqpNDDlGly0SdVTCqpAmwdOFWilDdQKbLdo9RfD6IDNMOdo9tIsEaTXkconlQ==", + "dependencies": { + "@babel/runtime": "^7.25.4", + "complex.js": "^2.1.1", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^4.3.7", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.2.1" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", @@ -12562,6 +12654,11 @@ "esprima": "~4.0.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/registry-auth-token": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", @@ -12587,7 +12684,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12741,6 +12837,11 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "node_modules/semantic-release": { "version": "21.1.2", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-21.1.2.tgz", @@ -13676,6 +13777,11 @@ "node": ">= 6" } }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -13990,6 +14096,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.1.tgz", + "integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==", + "engines": { + "node": ">= 18" + } + }, "node_modules/typescript": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", @@ -14378,6 +14492,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "packages/applications": { + "name": "@lib/application", + "version": "1.0.0", + "license": "MIT" + }, "packages/client": { "name": "@lib/client", "version": "1.0.0", @@ -14386,7 +14505,13 @@ "packages/config": { "name": "@lib/config", "version": "1.0.0", - "license": "MIT" + "license": "MIT", + "dependencies": { + "ajv": "^8.17.1", + "ajv-errors": "^3.0.0", + "ajv-keywords": "^5.1.0", + "mathjs": "^13.1.1" + } }, "packages/cookies": { "name": "@lib/cookies", diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 9bd93f1..deb6cd2 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,5 +1,5 @@ import createAzionApplicationClient, { AzionApplicationsClient } from 'azion/applications'; -import { defineConfig } from 'azion/config'; +import { defineConfig, processConfig } from 'azion/config'; import createDomainsClient, { AzionDomainsClient } from 'azion/domains'; import createPurgeClient, { AzionPurgeClient } from 'azion/purge'; import createSqlClient, { AzionSQLClient } from 'azion/sql'; @@ -78,7 +78,7 @@ function createClient({ token, options }: AzionClientConfig = {}): AzionClient { return client; } -export { createClient, defineConfig }; +export { createClient, defineConfig, processConfig }; export default createClient; diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 1407803..c04f75a 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -7,6 +7,7 @@ "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, + "moduleResolution": "node", "paths": { "azion": ["../client/src/index.ts"], "azion/sql": ["../sql/src/index.ts"], diff --git a/packages/config/.gitignore b/packages/config/.gitignore new file mode 100644 index 0000000..76add87 --- /dev/null +++ b/packages/config/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/packages/config/README.md b/packages/config/README.md index 080acbc..ab74d98 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -7,9 +7,12 @@ This module provides a function to configure and validate options for the Azion - [Installation](#installation) - [Usage](#usage) - [Example Configuration](#example-configuration) + - [Example Process Configuration](#example-process-configuration) - [API Reference](#api-reference) - [`defineConfig`](#defineconfig) + - [`processConfig`](#processconfig) - [Types](#types) + - [`AzionBuild`](#azionbuild) - [`AzionConfig`](#azionconfig) - [`AzionDomain`](#aziondomain) - [`AzionOrigin`](#azionorigin) @@ -115,6 +118,28 @@ const config = defineConfig({ }); ``` +### Example Process Configuration + +```javascript +import { processConfig } from 'azion'; + +const config = {...}; + +const manifest = processConfig(config); + +console.log(manifest); +``` + +```typescript +import { AzionConfig, processConfig } from 'azion'; + +const config: AzionConfig = {...}; + +const manifest = processConfig(config); + +console.log(manifest); +``` + ## API Reference ### `defineConfig` @@ -125,18 +150,41 @@ Configures and validates the options for the Azion Edge Application. - `config: AzionConfig` - The configuration object for the Azion Edge Application. +### `processConfig` + +Processes the configuration object and returns a manifest. + +**Parameters:** + +- `config: AzionConfig` - The configuration object for the Azion Edge Application. + ## Types ### `AzionConfig` **Properties:** -- `domain: AzionDomain` - The domain object. +- `build?: AzionBuild` - The build configuration. +- `domain?: AzionDomain` - The domain object. - `origin?: AzionOrigin[]` - List of origins. - `cache?: AzionCache[]` - List of cache settings. - `rules?: AzionRules[]` - List of edge rules. - `purge?: AzionPurge[]` - List of URLs or CacheKeys to purge. +### `AzionBuild` + +Type definition for the build configuration. + +**Properties:** + +- `builder?: 'esbuild' | 'webpack'` - The builder to use. +- `preset?: { name: string; }` - The preset to use. +- `entry?: string` - The entry file. +- `polyfills?: boolean` - Whether to include polyfills. +- `worker?: boolean` - Whether to build a owner worker. +- `custom?: Record` - Custom build configuration. +- `memoryFS?: { injectionDirs: string[], removePathPrefix: string }` - Memory file system configuration. + ### `AzionDomain` Type definition for the domain configuration. @@ -146,7 +194,8 @@ Type definition for the domain configuration. - `name: string` - The domain name. - `cnameAccessOnly?: boolean` - Whether to restrict access only to CNAMEs. - `cnames?: string[]` - List of CNAMEs for the domain. -- `Id?: number` - ID of the edge application. +- `id?: number` - ID of the edge application. +- `edgeApplicationId?: number` - ID of the edge application. - `edgeFirewallId?: number` - ID of the edge firewall. - `digitalCertificateId?: string | number | null` - ID of the digital certificate. - `mtls?: MTLSConfig` - Configuration for mTLS. diff --git a/packages/config/jest.config.js b/packages/config/jest.config.js new file mode 100644 index 0000000..45a6ff8 --- /dev/null +++ b/packages/config/jest.config.js @@ -0,0 +1,10 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + displayName: 'Config', + preset: 'ts-jest', + transform: { + '^.+\\.(t|j)s?$': '@swc/jest', + }, + testPathIgnorePatterns: ['/node_modules/', '/dist/'], + testEnvironment: 'node', +}; diff --git a/packages/config/package.json b/packages/config/package.json index 917a5c1..06887c3 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -9,12 +9,21 @@ "compile": "tsup --config ../../tsup.config.json", "lint": "eslint .", "lint:fix": "eslint --fix .", - "prettier": "prettier --write ." + "prettier": "prettier --write .", + "test": "jest --clearCache && jest -c jest.config.js .", + "test:watch": "jest -c jest.config.js . --watch", + "test:coverage": "jest --clearCache && jest -c jest.config.js . --coverage" }, "author": "aziontech", "license": "MIT", "files": [ "dist", "package.json" - ] + ], + "dependencies": { + "ajv": "^8.17.1", + "ajv-errors": "^3.0.0", + "ajv-keywords": "^5.1.0", + "mathjs": "^13.1.1" + } } diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index b1ded23..ff59d6f 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -1,3 +1,4 @@ +import { processConfig, validateConfig } from './processConfig/index'; import { AzionConfig } from './types'; /** @@ -5,6 +6,18 @@ import { AzionConfig } from './types'; * * @param {AzionConfig} config - The configuration object for the Azion Edge Application. * + * @param {Object} [config.build] - Configuration for the build. + * @param {string} config.build.builder - Bundler to use for the build. + * @param {string} config.build.entry - Entry file for the build. + * @param {Object} config.build.preset - Preset configuration for the build. + * @param {string} config.build.preset.name - Name of the preset. + * @param {Object} [config.build.memoryFS] - Configuration for the MemoryFS. + * @param {string[]} config.build.memoryFS.injectionDirs - List of directories to inject. + * @param {string} config.build.memoryFS.removePathPrefix - Path prefix to remove. + * @param {boolean} [config.build.polyfills] - Whether to enable polyfills. + * @param {boolean} [config.build.worker] - Whether to use the owner worker with addEventListener. + * @param {Object} [config.build.custom] - Custom configuration for the bundler. + * @param {Object} [config.build.custom.*] - Custom configuration options for the bundler. * @param {Object} [config.domain] - Configuration for the domain. * @param {string} config.domain.name - The domain name. * @param {boolean} [config.domain.cnameAccessOnly] - Whether to restrict access only to CNAMEs. @@ -125,6 +138,13 @@ import { AzionConfig } from './types'; * * @example * const config = AzionConfig({ + * build: { + * builder: 'webpack', + * preset: { + * name: 'react', + * }, + * polyfills: true, + * }, * domain: { * name: 'example.com', * cnameAccessOnly: false, @@ -186,8 +206,11 @@ import { AzionConfig } from './types'; * // ... other configurations * }); */ -export function defineConfig(config: AzionConfig): AzionConfig { +function defineConfig(config: AzionConfig): AzionConfig { + validateConfig(config); return config; } +export { defineConfig, processConfig }; + export type * from './types'; diff --git a/packages/config/src/processConfig/helpers/azion.config.example.ts b/packages/config/src/processConfig/helpers/azion.config.example.ts new file mode 100644 index 0000000..ea8519c --- /dev/null +++ b/packages/config/src/processConfig/helpers/azion.config.example.ts @@ -0,0 +1,328 @@ +export default { + build: { + entry: './src/index.js', + preset: { + name: 'angular', + }, + }, + domain: { + name: 'my_domain', + cnameAccessOnly: false, // Optional, defaults to false + cnames: ['www.example.com'], // Optional + edgeApplicationId: 12345, // Optional + edgeFirewallId: 12345, // Optional + digitalCertificateId: 'lets_encrypt', // 'lets_encrypt' or null + mtls: { + verification: 'enforce', // 'enforce' or 'permissive' + trustedCaCertificateId: 12345, + crlList: [111, 222], + }, // Optional + }, + origin: [ + { + id: 123, // Optional. ID of your origin. Obtain this value via GET request. Cannot be changed via API. + key: 'myorigin', // Optional. Key of your origin. Obtain this value via GET request. Cannot be changed via API. + name: 'myneworigin', // Required + type: 'single_origin', // Required. single_origin, load_balancer, object_storage, live_ingest. Defaults to single_origin if not provided + path: '', // Optional. Default '' if not provided + addresses: [ + // Required for single_origin, load_balancer, live_ingest. Optional for object_storage + // or addresses: ['http.bin.org'] + { + address: 'http.bin.org', + weight: 1, // Optional. Assign a number from 1 to 10 to determine how much traffic a server can handle. + }, + ], + protocolPolicy: 'preserve', // Optional. preserve, https, http. Defaults to preserve if not provided + hostHeader: '${host}', // Defaults to '${host}' if not provided + connectionTimeout: 60, // Optional. Default 60 if not provided + timeoutBetweenBytes: 120, // Optional. Default 120 if not provided + redirection: false, // Optional. Default false if not provided + hmac: { + region: 'us-east-1', // Required for hmac + accessKey: 'myaccesskey', // Required for hmac + secretKey: 'secretKey', // Required for hmac + }, // Optional + }, + { + id: 456, // Optional. ID of your origin. Obtain this value via GET request. Cannot be changed via API. + key: 'myorigin', // Optional. Key of your origin. Obtain this value via GET request. Cannot be changed via API. + name: 'myneworigin', // Required + type: 'object_storage', // Required. single_origin, load_balancer, object_storage, live_ingest. Defaults to single_origin if not provided + bucket: 'blue-courage', // Required for object_storage + prefix: '0101010101001', // Optional. Default '' if not provided + }, + ], + cache: [ + { + name: 'mycache', + stale: false, + queryStringSort: false, + methods: { + post: false, + options: false, + }, + browser: { + maxAgeSeconds: 1000 * 5, // 5000 seconds + }, + edge: { + maxAgeSeconds: 1000, + }, + cacheByQueryString: { + option: 'blacklist', // ['blacklist', 'whitelist', 'varies', 'ignore] + list: ['order', 'user'], + }, + cacheByCookie: { + option: 'whitelist', // ['blacklist', 'whitelist', 'varies', 'ignore] + list: ['session', 'user'], + }, + }, + ], + rules: { + request: [ + { + name: 'rewriteRuleExample', + description: 'Rewrite URLs, set cookies and headers, forward cookies.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/rewrite$', + behavior: { + setCache: 'mycache1', + rewrite: `/new/%{captured[1]}`, // Rewrites /original/image.jpg to /new/image.jpg + setCookie: 'user=12345; Path=/; Secure', + setHeaders: 'Cache-Control: no-cache', + forwardCookies: true, + }, + }, + { + name: 'staticContentRuleExample', + description: + 'Handle static content by setting a specific origin and delivering directly.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/_statics/', + behavior: { + setOrigin: { + name: 'myneworigin', + type: 'object_storage', + }, + deliver: true, + }, + }, + { + name: 'computeFunctionRuleExample', + description: 'Executes a serverless function for compute paths.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/compute/', + behavior: { + runFunction: { + path: '.edge/worker.js', + }, + }, + }, + { + name: 'permanentRedirectRuleExample', + description: 'Permanently redirects from an old URL to a new URL.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/old-url$', + behavior: { + redirectTo301: 'https://newsite.com/new-url', + }, + }, + { + name: 'gzipCompressionRuleExample', + description: 'Enables GZIP compression for specified paths.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/compress', + behavior: { + enableGZIP: true, + }, + }, + { + name: 'apiHeaderRuleExample', + description: 'Sets multiple headers for API responses.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/api', + behavior: { + setHeaders: ['X-API-Version: 1', 'X-Frame-Options: deny'], + }, + }, + { + name: 'cookieSettingRuleExample', + description: 'Sets a secure, HttpOnly cookie.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/check', + behavior: { + setCookie: 'test=12345; Path=/; Secure; HttpOnly', + }, + }, + { + name: 'userCaptureRuleExample', + description: 'Captures user ID from the URL using regex.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/user/(.*)', + behavior: { + capture: { + regex: '^(.*)$', + captured: 'user_id', + subject: 'uri', + }, + }, + }, + { + name: 'directCacheRuleExample', + description: 'Directly sets caching policies within the rule.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/some-path', + behavior: { + setCache: { + name: 'dynamicCache', + stale: true, + queryStringSort: true, + methods: { + post: true, + options: true, + }, + browser: { + maxAgeSeconds: 3600, // 1 hour + }, + edge: { + maxAgeSeconds: 600, // 10 minutes + }, + }, + }, + }, + { + name: 'bypassCacheRuleExample', + description: + 'Ensures data is always fetched fresh, bypassing any cache.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/bypass', + behavior: { + bypassCache: true, + }, + }, + { + name: 'forceHttpsRuleExample', + description: 'Redirects HTTP requests to HTTPS for secure areas.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/secure-area', + behavior: { + httpToHttps: true, + }, + }, + { + name: 'UriRedirectExample', + description: 'Uses the captured path as part of the new URL.', + active: true, + match: '.*', // Captures all URIs + variable: 'uri', // Defines the variable to be captured + behavior: { + capture: { + match: '^(.*)$', // Captures the entire URI path + captured: 'uri_path', // Name of the variable where the captured path will be stored + subject: 'uri', // Indicates that the capture will be made on the 'uri' variable + }, + redirectTo302: `https://example.com/%{uri_path}`, // Uses the captured path as part of the new URL + filterCookie: 'original_uri_cookie', // Removes the original cookie to avoid conflicts or duplicate information + }, + }, + ], + response: [ + { + name: 'apiDataResponseRuleExample', + description: + 'Manage headers, cookies, and GZIP compression for API data responses.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/api/data', + behavior: { + setHeaders: 'Content-Type: application/json', + setCookie: 'session=abcdef; Path=/; HttpOnly', + filterHeader: 'Server', + filterCookie: 'tracking', + enableGZIP: true, + }, + }, + { + name: 'userProfileRedirectRuleExample', + description: + 'Redirects user profile requests to a new profile page URL.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/user/profile', + behavior: { + redirectTo301: 'https://newsite.com/profile', + }, + }, + { + name: 'computeResultFunctionRuleExample', + description: + 'Runs a function and captures full path from the URI for compute results.', + active: true, + variable: 'uri', // Optional, defaults to 'uri' if not provided + match: '^/compute-result', + behavior: { + runFunction: { + path: '.edge/computeResult.js', + }, + // This rule captures the full URI path and stores it in a variable named 'full_path_arr'. + capture: { + match: '^(.*)$', // The regular expression '^(.*)$' captures the entire URI path. + captured: 'full_path_arr', // The result of the capture is stored in the variable 'full_path_arr'. + subject: 'uri', // The capture is based on the value of the 'uri' variable. + }, + // Permanently redirects to the first element captured in 'full_path_arr'. + redirectTo301: '%{full_path_arr[0]}', // Uses the first element of the 'full_path_arr' array as part of the new URL. + }, + }, + { + name: 'userProfileRedirectRuleExample', + description: 'Redirects user profile requests based on cookie value.', + active: true, + // eslint-disable-next-line no-template-curly-in-string + variable: 'cookie_name', // Example using cookie value + match: '^user-profile$', // Matches based on the cookie value + behavior: { + redirectTo301: 'https://newsite.com/profile', + }, + }, + { + name: 'temporaryPageRedirectRuleExample', + description: + 'Temporarily redirects an old page based on query parameters.', + active: true, + // eslint-disable-next-line no-template-curly-in-string + variable: 'args', // All query parameters + match: '^old-page$', // Matches based on the presence of specific query parameters + behavior: { + redirectTo302: 'https://newsite.com/new-page', + }, + }, + ], + }, + purge: [ + { + type: 'url', + urls: ['http://www.example.com/image.jpg'], + }, + { + type: 'cachekey', + urls: ['https://example.com/test1', 'https://example.com/test2'], + method: 'delete', + }, + { + type: 'wildcard', + urls: ['http://www.example.com/*'], + }, + ], +}; diff --git a/packages/config/src/processConfig/helpers/behaviors.ts b/packages/config/src/processConfig/helpers/behaviors.ts new file mode 100644 index 0000000..8fbbc5b --- /dev/null +++ b/packages/config/src/processConfig/helpers/behaviors.ts @@ -0,0 +1,211 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +export const requestBehaviors = { + setOrigin: { + transform: (value: any, payloadCDN: any) => { + const origin = payloadCDN.origin.find((o: any) => o.name === value.name && o.origin_type === value.type); + + if (!origin) { + throw new Error(`Rule setOrigin name '${value.name}' not found in the origin settings`); + } else if (origin.origin_type !== value.type) { + throw new Error(`Rule setOrigin originType '${value.type}' does not match the origin settings`); + } + + return { + name: 'set_origin', + target: origin.name, + }; + }, + }, + rewrite: { + transform: (value: any) => { + const behaviors = []; + behaviors.push({ + name: 'rewrite_request', + target: value, + }); + return behaviors; + }, + }, + deliver: { + transform: () => ({ + name: 'deliver', + target: null, + }), + }, + setCookie: { + transform: (value: any) => ({ + name: 'add_request_cookie', + target: value, + }), + }, + setHeaders: { + transform: (value: any) => + value.map((header: any) => ({ + name: 'add_request_header', + target: header, + })), + }, + setCache: { + transform: (value: any, payloadCDN: any) => { + if (typeof value === 'string') { + return { + name: 'set_cache_policy', + target: value, + }; + } + if (typeof value === 'object') { + const cacheSetting = { + name: value.name, + ...value, + }; + payloadCDN.cache.push(cacheSetting); + return { + name: 'set_cache_policy', + target: value.name, + }; + } + return undefined; + }, + }, + forwardCookies: { + transform: (value: any) => { + if (value) { + return { + name: 'forward_cookies', + target: null, + }; + } + return undefined; + }, + }, + runFunction: { + transform: (value: any) => ({ + name: 'run_function', + target: value.path, + }), + }, + enableGZIP: { + transform: (value: any) => { + if (value) { + return { + name: 'enable_gzip', + target: '', + }; + } + return undefined; + }, + }, + bypassCache: { + transform: (value: any) => { + if (value) { + return { + name: 'bypass_cache_phase', + target: null, + }; + } + return undefined; + }, + }, + httpToHttps: { + transform: (value: any) => { + if (value) { + return { + name: 'redirect_http_to_https', + target: null, + }; + } + return undefined; + }, + }, + redirectTo301: { + transform: (value: any) => ({ + name: 'redirect_to_301', + target: value, + }), + }, + redirectTo302: { + transform: (value: any) => ({ + name: 'redirect_to_302', + target: value, + }), + }, + capture: { + transform: (value: any) => ({ + name: 'capture_match_groups', + target: { + regex: value.match, + captured_array: value.captured, + subject: `\${${value.subject ?? 'uri'}}`, + }, + }), + }, + // Adicione mais comportamentos conforme necessário +}; + +export const responseBehaviors = { + setCookie: { + transform: (value: any) => ({ + name: 'set_cookie', + target: value, + }), + }, + setHeaders: { + transform: (value: any) => + value.map((header: any) => ({ + name: 'add_response_header', + target: header, + })), + }, + enableGZIP: { + transform: (value: any) => { + if (value) { + return { + name: 'enable_gzip', + target: '', + }; + } + return undefined; + }, + }, + filterCookie: { + transform: (value: any) => ({ + name: 'filter_response_cookie', + target: value, + }), + }, + filterHeader: { + transform: (value: any) => ({ + name: 'filter_response_header', + target: value, + }), + }, + runFunction: { + transform: (value: any) => ({ + name: 'run_function', + target: value.path, + }), + }, + redirectTo301: { + transform: (value: any) => ({ + name: 'redirect_to_301', + target: value, + }), + }, + redirectTo302: { + transform: (value: any) => ({ + name: 'redirect_to_302', + target: value, + }), + }, + capture: { + transform: (value: any) => ({ + name: 'capture_match_groups', + target: { + regex: value.match, + captured_array: value.captured, + subject: `\${${value.subject ?? 'uri'}}`, + }, + }), + }, + // Adicione mais comportamentos conforme necessário +}; diff --git a/packages/config/src/processConfig/helpers/convertLegacyConfig.ts b/packages/config/src/processConfig/helpers/convertLegacyConfig.ts new file mode 100644 index 0000000..6f8e44a --- /dev/null +++ b/packages/config/src/processConfig/helpers/convertLegacyConfig.ts @@ -0,0 +1,68 @@ +/** + * Converts legacy configuration to the new format by moving old properties into the `behavior` object. + * This ensures backward compatibility for projects that do not use the `behavior` field. + * @param {object} config - The configuration object to be converted. + * @returns The converted configuration object with `behavior` properties. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function convertLegacyConfig(config: any) { + const newConfig = { ...config }; + + // Define the properties that should be moved to the behavior object for request and response + const requestBehaviorProperties = [ + 'httpToHttps', + 'bypassCache', + 'forwardCookies', + 'capture', + 'setOrigin', + 'rewrite', + 'setCookie', + 'setHeaders', + 'runFunction', + 'setCache', + 'redirectTo301', + 'redirectTo302', + 'deliver', + ]; + + const responseBehaviorProperties = [ + 'enableGZIP', + 'capture', + 'setCookie', + 'setHeaders', + 'filterHeader', + 'filterCookie', + 'runFunction', + 'redirectTo301', + 'redirectTo302', + 'deliver', + ]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const convertRules = (rules: any, behaviorProperties: any) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return rules.map((rule: any) => { + const newRule = { ...rule, behavior: { ...rule.behavior } }; + + // Preserve the order of properties + Object.keys(rule).forEach((prop) => { + if (behaviorProperties.includes(prop)) { + newRule.behavior[prop] = rule[prop]; + delete newRule[prop]; + } + }); + + return newRule; + }); + }; + + if (newConfig.rules && newConfig.rules.request) { + newConfig.rules.request = convertRules(newConfig.rules.request, requestBehaviorProperties); + } + + if (newConfig.rules && newConfig.rules.response) { + newConfig.rules.response = convertRules(newConfig.rules.response, responseBehaviorProperties); + } + return newConfig; +} + +export default convertLegacyConfig; diff --git a/packages/config/src/processConfig/helpers/schema.ts b/packages/config/src/processConfig/helpers/schema.ts new file mode 100644 index 0000000..5891f25 --- /dev/null +++ b/packages/config/src/processConfig/helpers/schema.ts @@ -0,0 +1,845 @@ +const azionConfigSchema = { + type: 'object', + properties: { + build: { + type: 'object', + properties: { + entry: { + type: 'string', + errorMessage: "The 'entry' field must be a string representing the entry point file path.", + }, + builder: { + type: ['string', 'null'], + enum: ['esbuild', 'webpack', null], + errorMessage: "The 'builder' field must be either 'esbuild', 'webpack', or null.", + }, + polyfills: { + type: 'boolean', + errorMessage: "The 'polyfills' field must be a boolean.", + }, + worker: { + type: 'boolean', + errorMessage: "The 'worker' field must be a boolean.", + }, + preset: { + type: 'object', + properties: { + name: { + type: 'string', + errorMessage: "The 'preset.name' field must be a string.", + }, + }, + required: ['name'], + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'preset' object.", + required: "The 'name' and 'mode' fields are required in the 'preset' object.", + }, + }, + memoryFS: { + type: 'object', + properties: { + injectionDirs: { + type: ['array', 'null'], + items: { + type: 'string', + }, + errorMessage: "The 'memoryFS.injectionDirs' field must be an array of strings or null.", + }, + removePathPrefix: { + type: ['string', 'null'], + errorMessage: "The 'memoryFS.removePathPrefix' field must be a string or null.", + }, + }, + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'memoryFS' object.", + }, + }, + custom: { + type: 'object', + additionalProperties: true, + errorMessage: "The 'custom' field must be an object.", + }, + }, + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'build' object.", + }, + }, + origin: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'integer', + errorMessage: "The 'id' field must be a number.", + }, + key: { + type: 'string', + errorMessage: "The 'key' field must be a string.", + }, + name: { + type: 'string', + errorMessage: "The 'name' field must be a string.", + }, + type: { + type: 'string', + enum: ['single_origin', 'object_storage', 'load_balancer', 'live_ingest'], + errorMessage: + "The 'type' field must be a string and one of 'single_origin', 'object_storage', 'load_balancer' or 'live_ingest'.", + }, + bucket: { + type: ['string', 'null'], + errorMessage: "The 'bucket' field must be a string or null.", + }, + prefix: { + type: ['string', 'null'], + errorMessage: "The 'prefix' field must be a string or null.", + }, + addresses: { + anyOf: [ + { + type: 'array', + items: { + type: 'string', + }, + errorMessage: { + type: "The 'addresses' field must be an array of strings.", + }, + }, + { + type: 'array', + items: { + type: 'object', + properties: { + address: { + type: 'string', + errorMessage: "The 'address' field must be a string.", + }, + weight: { + type: 'integer', + }, + }, + required: ['address'], + additionalProperties: false, + errorMessage: { + type: "The 'addresses' field must be an array of objects.", + additionalProperties: 'No additional properties are allowed in address items.', + required: "The 'address' field is required in each address item.", + }, + }, + }, + ], + }, + hostHeader: { + type: 'string', + errorMessage: "The 'hostHeader' field must be a string.", + }, + protocolPolicy: { + type: 'string', + enum: ['preserve', 'http', 'https'], + errorMessage: + "The 'protocolPolicy' field must be either 'http', 'https' or 'preserve'. Default is 'preserve'.", + }, + redirection: { + type: 'boolean', + errorMessage: "The 'redirection' field must be a boolean.", + }, + method: { + type: 'string', + enum: ['ip_hash', 'least_connections', 'round_robin'], + errorMessage: + "The 'method' field must be either 'ip_hash', 'least_connections' or 'round_robin'. Default is 'ip_hash'.", + }, + path: { + type: 'string', + errorMessage: "The 'path' field must be a string.", + }, + connectionTimeout: { + type: 'integer', + errorMessage: "The 'connectionTimeout' field must be a number. Default is 60.", + }, + timeoutBetweenBytes: { + type: 'integer', + errorMessage: "The 'timeoutBetweenBytes' field must be a number. Default is 120.", + }, + hmac: { + type: 'object', + properties: { + region: { + type: 'string', + errorMessage: "The 'region' field must be a string.", + }, + accessKey: { + type: 'string', + errorMessage: "The 'accessKey' field must be a string.", + }, + secretKey: { + type: 'string', + errorMessage: "The 'secretKey' field must be a string.", + }, + }, + required: ['region', 'accessKey', 'secretKey'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in the hmac object.', + required: "The 'region, accessKey and secretKey' fields are required in the hmac object.", + }, + }, + }, + required: ['name', 'type'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in origin item objects.', + required: "The 'name and type' field is required in each origin item.", + }, + }, + errorMessage: { + additionalProperties: "The 'origin' field must be an array of objects.", + }, + }, + cache: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + errorMessage: "The 'name' field must be a string.", + }, + stale: { + type: 'boolean', + errorMessage: "The 'stale' field must be a boolean.", + }, + queryStringSort: { + type: 'boolean', + errorMessage: "The 'queryStringSort' field must be a boolean.", + }, + methods: { + type: 'object', + properties: { + post: { + type: 'boolean', + errorMessage: "The 'post' field must be a boolean.", + }, + options: { + type: 'boolean', + errorMessage: "The 'options' field must be a boolean.", + }, + }, + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'methods' object.", + }, + }, + browser: { + type: 'object', + properties: { + maxAgeSeconds: { + oneOf: [ + { + type: 'number', + errorMessage: "The 'maxAgeSeconds' field must be a number or a valid mathematical expression.", + }, + { + type: 'string', + pattern: '^[0-9+*/.() -]+$', + errorMessage: "The 'maxAgeSeconds' field must be a valid mathematical expression.", + }, + ], + }, + }, + required: ['maxAgeSeconds'], + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'browser' object.", + required: "The 'maxAgeSeconds' field is required in the 'browser' object.", + }, + }, + edge: { + type: 'object', + properties: { + maxAgeSeconds: { + oneOf: [ + { + type: 'number', + errorMessage: "The 'maxAgeSeconds' field must be a number or a valid mathematical expression.", + }, + { + type: 'string', + pattern: '^[0-9+*/.() -]+$', + errorMessage: "The 'maxAgeSeconds' field must be a valid mathematical expression.", + }, + ], + }, + }, + required: ['maxAgeSeconds'], + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'edge' object.", + required: "The 'maxAgeSeconds' field is required in the 'edge' object.", + }, + }, + cacheByCookie: { + type: 'object', + properties: { + option: { + type: 'string', + enum: ['ignore', 'varies', 'whitelist', 'blacklist'], + errorMessage: "The 'option' field must be one of 'ignore', 'varies', 'whitelist' or 'blacklist'..", + }, + list: { + type: 'array', + items: { + type: 'string', + errorMessage: "Each item in 'list' must be a string.", + }, + errorMessage: { + type: "The 'list' field must be an array of strings.", + }, + }, + }, + required: ['option'], + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'cacheByCookie' object.", + required: "The 'option' field is required in the 'cacheByCookie' object.", + }, + if: { + properties: { + option: { enum: ['whitelist', 'blacklist'] }, + }, + }, + then: { + required: ['list'], + errorMessage: { + required: "The 'list' field is required when 'option' is 'whitelist' or 'blacklist'.", + }, + }, + }, + + cacheByQueryString: { + type: 'object', + properties: { + option: { + type: 'string', + enum: ['ignore', 'varies', 'whitelist', 'blacklist'], + errorMessage: "The 'option' field must be one of 'ignore', 'varies', 'whitelist' or 'blacklist'.", + }, + list: { + type: 'array', + items: { + type: 'string', + errorMessage: "Each item in 'list' must be a string.", + }, + errorMessage: { + type: "The 'list' field must be an array of strings.", + }, + }, + }, + required: ['option'], + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'cacheByQueryString' object.", + required: "The 'option' field is required in the 'cacheByQueryString' object.", + }, + if: { + properties: { + option: { enum: ['whitelist', 'blacklist'] }, + }, + }, + then: { + required: ['list'], + errorMessage: { + required: "The 'list' field is required when 'option' is 'whitelist' or 'blacklist'.", + }, + }, + }, + }, + required: ['name'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in cache item objects.', + required: "The 'name' field is required in each cache item.", + }, + }, + errorMessage: { + additionalProperties: "The 'cache' field must be an array of objects.", + }, + }, + rules: { + type: 'object', + properties: { + request: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + errorMessage: "The 'name' field must be a string.", + }, + description: { + type: 'string', + errorMessage: "The 'description' field must be a string.", + }, + active: { + type: 'boolean', + default: true, + errorMessage: "The 'active' field must be a boolean.", + }, + match: { + type: 'string', + errorMessage: "The 'match' field must be a string.", + }, + variable: { + type: 'string', + errorMessage: "The 'variable' field must be a string.", + }, + behavior: { + type: 'object', + properties: { + setOrigin: { + type: 'object', + properties: { + name: { + type: 'string', + errorMessage: "The 'name' field must be a string.", + }, + type: { + type: 'string', + errorMessage: "The 'type' field must be a string.", + }, + }, + required: ['name', 'type'], + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'setOrigin' object.", + required: "The 'name or type' field is required in the 'setOrigin' object.", + }, + }, + rewrite: { + type: 'string', + errorMessage: "The 'rewrite' field must be a string.", + }, + setHeaders: { + type: 'array', + items: { + type: 'string', + errorMessage: "Each item in 'setHeaders' must be a string.", + }, + errorMessage: { + type: "The 'setHeaders' field must be an array of strings.", + }, + }, + bypassCache: { + type: ['boolean', 'null'], + errorMessage: "The 'bypassCache' field must be a boolean or null.", + }, + httpToHttps: { + type: ['boolean', 'null'], + errorMessage: "The 'httpToHttps' field must be a boolean or null.", + }, + redirectTo301: { + type: ['string', 'null'], + errorMessage: "The 'redirectTo301' field must be a string or null.", + }, + redirectTo302: { + type: ['string', 'null'], + errorMessage: "The 'redirectTo302' field must be a string or null.", + }, + forwardCookies: { + type: ['boolean', 'null'], + errorMessage: "The 'forwardCookies' field must be a boolean or null.", + }, + setCookie: { + type: ['string', 'null'], + errorMessage: "The 'setCookie' field must be a string or null.", + }, + deliver: { + type: ['boolean', 'null'], + errorMessage: "The 'deliver' field must be a boolean or null.", + }, + capture: { + type: 'object', + properties: { + match: { + type: 'string', + errorMessage: "The 'match' field must be a string.", + }, + captured: { + type: 'string', + errorMessage: "The 'captured' field must be a string.", + }, + subject: { + type: 'string', + errorMessage: "The 'subject' field must be a string.", + }, + }, + required: ['match', 'captured', 'subject'], + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'capture' object.", + required: "All properties ('match', 'captured', 'subject') are required in the 'capture' object.", + }, + }, + runFunction: { + type: 'object', + properties: { + path: { + type: 'string', + errorMessage: "The 'path' field must be a string.", + }, + name: { + type: ['string', 'null'], + errorMessage: "The 'name' field can be a string or null.", + }, + }, + required: ['path'], + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'runFunction' object.", + required: "The 'path' field is required in the 'runFunction' object.", + }, + }, + setCache: { + oneOf: [ + { + type: 'string', + errorMessage: "The 'setCache' field must be a string.", + }, + { + type: 'object', + properties: { + name: { + type: 'string', + errorMessage: "The 'name' field must be a string.", + }, + browser_cache_settings_maximum_ttl: { + type: 'number', + nullable: true, + errorMessage: "The 'browser_cache_settings_maximum_ttl' field must be a number or null.", + }, + cdn_cache_settings_maximum_ttl: { + type: 'number', + nullable: true, + errorMessage: "The 'cdn_cache_settings_maximum_ttl' field must be a number or null.", + }, + }, + required: ['name'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in the cache object.', + required: "The 'name' field is required in the cache object.", + }, + }, + ], + errorMessage: "The 'cache' field must be either a string or an object with specified properties.", + }, + }, + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'behavior' object.", + }, + }, + }, + required: ['name', 'match'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in request items.', + required: "The 'name' and 'match' fields are required in each request item.", + }, + }, + }, + response: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + errorMessage: "The 'name' field must be a string.", + }, + description: { + type: 'string', + errorMessage: "The 'description' field must be a string.", + }, + active: { + type: 'boolean', + default: true, + errorMessage: "The 'active' field must be a boolean.", + }, + match: { + type: 'string', + errorMessage: "The 'match' field must be a string.", + }, + variable: { + type: 'string', + errorMessage: "The 'variable' field must be a string.", + }, + behavior: { + type: 'object', + properties: { + setCookie: { + type: ['string', 'null'], + errorMessage: "The 'setCookie' field must be a string or null.", + }, + setHeaders: { + type: 'array', + items: { + type: 'string', + errorMessage: "Each item in 'setHeaders' must be a string.", + }, + errorMessage: { + type: "The 'setHeaders' field must be an array of strings.", + }, + }, + deliver: { + type: ['boolean', 'null'], + errorMessage: "The 'deliver' field must be a boolean or null.", + }, + capture: { + type: 'object', + properties: { + match: { + type: 'string', + errorMessage: "The 'match' field must be a string.", + }, + captured: { + type: 'string', + errorMessage: "The 'captured' field must be a string.", + }, + subject: { + type: 'string', + errorMessage: "The 'subject' field must be a string.", + }, + }, + required: ['match', 'captured', 'subject'], + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'capture' object.", + required: "All properties ('match', 'captured', 'subject') are required in the 'capture' object.", + }, + }, + enableGZIP: { + type: ['boolean', 'null'], + errorMessage: "The 'enableGZIP' field must be a boolean or null.", + }, + filterCookie: { + type: ['string', 'null'], + errorMessage: "The 'filterCookie' field must be a string or null.", + }, + filterHeader: { + type: ['string', 'null'], + errorMessage: "The 'filterHeader' field must be a string or null.", + }, + runFunction: { + type: 'object', + properties: { + path: { + type: 'string', + errorMessage: "The 'path' field must be a string.", + }, + name: { + type: ['string', 'null'], + errorMessage: "The 'name' field can be a string or null.", + }, + }, + required: ['path'], + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'runFunction' object.", + required: "The 'path' field is required in the 'runFunction' object.", + }, + }, + redirectTo301: { + type: ['string', 'null'], + errorMessage: "The 'redirectTo301' field must be a string or null.", + }, + redirectTo302: { + type: ['string', 'null'], + errorMessage: "The 'redirectTo302' field must be a string or null.", + }, + }, + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'behavior' object.", + }, + }, + }, + required: ['name', 'match'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in response items.', + required: "The 'name' and 'match' fields are required in each response item.", + }, + }, + }, + }, + anyOf: [ + { + required: ['request'], + }, + { + required: ['response'], + }, + ], + additionalProperties: false, + errorMessage: { + additionalProperties: "No additional properties are allowed in the 'rules' object.", + anyOf: "Either 'request' or 'response' must be provided.", + }, + }, + networkList: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'number', + errorMessage: "The 'id' field must be a number.", + }, + listType: { + type: 'string', + errorMessage: "The 'listType' field must be a string.", + }, + listContent: { + type: 'array', + items: { + type: 'string', + errorMessage: "The 'listContent' field must be an array of strings.", + }, + }, + }, + required: ['id', 'listType', 'listContent'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in network list items.', + required: "The 'id, listType and listContent' fields are required in each network list item.", + }, + }, + }, + domain: { + type: 'object', + properties: { + name: { + type: 'string', + errorMessage: "The 'name' field must be a string.", + }, + cnameAccessOnly: { + type: 'boolean', + errorMessage: "The 'cnameAccessOnly' field must be a boolean.", + }, + cnames: { + type: 'array', + items: { + type: 'string', + errorMessage: "Each item in 'cnames' must be a string.", + }, + errorMessage: { + type: "The 'cnames' field must be an array of strings.", + }, + }, + edgeApplicationId: { + type: 'number', + errorMessage: "The 'edgeApplicationId' field must be a number.", + }, + edgeFirewallId: { + type: 'number', + errorMessage: "The 'edgeFirewallId' field must be a number.", + }, + digitalCertificateId: { + type: ['string', 'number', 'null'], + errorMessage: + "The 'digitalCertificateId' field must be a string, number or null. If string, it must be 'lets_encrypt'.", + }, + mtls: { + type: 'object', + properties: { + verification: { + type: 'string', + enum: ['enforce', 'permissive'], + errorMessage: "The 'verification' field must be a string.", + }, + trustedCaCertificateId: { + type: 'number', + errorMessage: "The 'trustedCaCertificateId' field must be a number.", + }, + crlList: { + type: 'array', + items: { + type: 'number', + errorMessage: "Each item in 'crlList' must be a number.", + }, + errorMessage: { + type: "The 'crlList' field must be an array of numbers.", + }, + }, + }, + required: ['verification', 'trustedCaCertificateId'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in the mtls object.', + required: "The 'verification and trustedCaCertificateId' fields are required in the mtls object.", + }, + }, + }, + required: ['name'], + additionalProperties: false, + errorMessage: { + type: "The 'domain' field must be an object.", + additionalProperties: 'No additional properties are allowed in the domain object.', + required: "The 'name' field is required in the domain object.", + }, + }, + purge: { + type: 'array', + items: { + type: 'object', + properties: { + type: { + type: 'string', + enum: ['url', 'cachekey', 'wildcard'], + errorMessage: "The 'type' field must be either 'url', 'cachekey' or 'wildcard'.", + }, + urls: { + type: 'array', + items: { + type: 'string', + errorMessage: "Each item in 'urls' must be a string.", + }, + errorMessage: { + type: "The 'urls' field must be an array of strings.", + }, + }, + method: { + type: 'string', + enum: ['delete'], + errorMessage: "The 'method' field must be either 'delete'. Default is 'delete'.", + }, + layer: { + type: 'string', + enum: ['edge_caching', 'l2_caching'], + errorMessage: "The 'layer' field must be either 'edge_caching' or 'l2_caching'. Default is 'edge_caching'.", + }, + }, + required: ['type', 'urls'], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in purge items.', + required: "The 'type and urls' fields are required in each purge item.", + }, + }, + }, + }, + required: [], + additionalProperties: false, + errorMessage: { + additionalProperties: 'No additional properties are allowed in the configuration object.', + required: "The 'rules' section is required in the configuration object.", + }, +}; + +export default azionConfigSchema; diff --git a/packages/config/src/processConfig/index.test.ts b/packages/config/src/processConfig/index.test.ts new file mode 100644 index 0000000..4d3aef5 --- /dev/null +++ b/packages/config/src/processConfig/index.test.ts @@ -0,0 +1,2276 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { processConfig, validateConfig } from '.'; +import { AzionConfig } from '../types'; + +describe('generate', () => { + describe('validateConfig', () => { + it('should validate the configuration object', () => { + const config = { + build: { + preset: { + name: 'next', + }, + polyfills: true, + custom: { + minify: true, + }, + }, + }; + expect(() => validateConfig(config)).not.toThrow(); + }); + + it('should throw an error if the configuration object is invalid', () => { + const config: any = { + build: { + preset: { + name: true, + }, + polyfills: true, + }, + }; + expect(() => validateConfig(config)).toThrow(); + }); + }); + + describe('processConfig', () => { + describe('Cache and Rules', () => { + it('should process config from the configuration object', () => { + const config = { + build: { + preset: { + name: 'next', + }, + polyfills: true, + custom: { + minify: true, + }, + }, + }; + expect(processConfig(config)).toEqual( + expect.objectContaining({ + build: { + preset: { + name: 'next', + }, + polyfills: true, + custom: { + minify: true, + }, + }, + }), + ); + }); + + it('should throw an error for invalid mathematical expressions', () => { + const azionConfig: any = { + cache: [ + { + name: 'testCache', + browser: { maxAgeSeconds: '2 * 3' }, + edge: { maxAgeSeconds: 'invalidExpression' }, + }, + ], + rules: { + request: [], + }, + }; + + expect(() => processConfig(azionConfig)).toThrow( + "The 'maxAgeSeconds' field must be a number or a valid mathematical expression.", + ); + }); + + it('should process a cache object directly in the rule', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/test', + behavior: { + setCache: { + name: 'directCache', + browser_cache_settings_maximum_ttl: 300, + cdn_cache_settings_maximum_ttl: 600, + }, + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result).toHaveProperty('cache'); + expect(result.cache).toEqual(expect.arrayContaining([expect.objectContaining({ name: 'directCache' })])); + }); + + it('should handle rewrites directly as a string', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'simpleRewriteRule', + match: '/simple', + behavior: { + rewrite: '/new-path', + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'rewrite_request', + target: '/new-path', + }), + ]), + ); + }); + + it('should correctly calculate numerical values', () => { + const azionConfig: any = { + cache: [ + { + name: 'calcCache', + browser: { maxAgeSeconds: '2 * 3' }, + edge: { maxAgeSeconds: '4 + 1' }, + }, + ], + rules: { + request: [], + }, + }; + + const result = processConfig(azionConfig); + expect(result.cache[0]).toEqual( + expect.objectContaining({ + browser_cache_settings_maximum_ttl: 6, + cdn_cache_settings_maximum_ttl: 5, + }), + ); + }); + + it('should correctly handle the absence of cache settings', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/no-cache', + behavior: { + runFunction: { + path: '.edge/worker.js', + }, + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.cache).toEqual([]); + }); + + it('should correctly convert request rules', () => { + const azionConfig: any = { + origin: [ + { + name: 'my origin storage', + type: 'object_storage', + bucket: 'mybucket', + prefix: 'myfolder', + }, + ], + rules: { + request: [ + { + name: 'testRule', + match: '/api', + behavior: { + setOrigin: { + name: 'my origin storage', + type: 'object_storage', + }, + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + criteria: expect.arrayContaining([ + expect.arrayContaining([ + expect.objectContaining({ + input_value: '/api', + }), + ]), + ]), + behaviors: expect.arrayContaining([ + expect.objectContaining({ + name: 'set_origin', + target: 'my origin storage', + }), + ]), + }), + ]), + ); + }); + + it('should correctly calculate complex mathematical expressions', () => { + const azionConfig: any = { + cache: [ + { + name: 'complexMathCache', + browser: { maxAgeSeconds: '(2 * 3) + 5' }, + edge: { maxAgeSeconds: '10 / 2' }, + }, + ], + rules: { + request: [], + }, + }; + + const result = processConfig(azionConfig); + expect(result.cache[0]).toEqual( + expect.objectContaining({ + browser_cache_settings_maximum_ttl: 11, + cdn_cache_settings_maximum_ttl: 5, + }), + ); + }); + + it('should throw an error when data types do not match the expected', () => { + const azionConfig: any = { + cache: [ + { + name: 'typeValidationCache', + browser: { maxAgeSeconds: true }, // Invalid boolean type for maxAgeSeconds + edge: { maxAgeSeconds: '10' }, + }, + ], + }; + + expect(() => processConfig(azionConfig)).toThrow( + "The 'maxAgeSeconds' field must be a number or a valid mathematical expression.", + ); + }); + + it('should correctly configure cookie forwarding when forwardCookies is true', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/', + behavior: { + forwardCookies: true, + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + // Checks if the forward_cookies behavior is included and set to true when forwardCookies is true + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'forward_cookies', + target: null, // Updated from 'params' to 'target' + }), + ]), + ); + }); + + it('should not include forward_cookies behavior when forwardCookies is false or not specified', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/no-forward', + behavior: { + forwardCookies: false, + }, + }, + { + name: 'testRule', + match: '/default-forward', + // forwardCookies not specified + }, + ], + }, + }; + + const result = processConfig(azionConfig); + // Checks if the forward_cookies behavior is not included when forwardCookies is false or not specified + expect(result.rules[0].behaviors).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'forward_cookies', + }), + ]), + ); + expect(result.rules[1].behaviors).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'forward_cookies', + }), + ]), + ); + }); + + it('should correctly process the setOrigin rule with all optional fields provided', () => { + const azionConfigWithAllFields = { + origin: [ + { + name: 'my origin storage', + type: 'object_storage', + bucket: 'mybucket', + prefix: 'myfolder', + }, + ], + rules: { + request: [ + { + name: 'testRule', + match: '/_next', + behavior: { + setOrigin: { + name: 'my origin storage', + type: 'object_storage', + }, + }, + }, + ], + }, + }; + const resultWithAllFields = processConfig(azionConfigWithAllFields); + expect(resultWithAllFields.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'set_origin', + target: 'my origin storage', + }), + ]), + ); + }); + + it('should throw an error when "type" is missing in "setOrigin"', () => { + const azionConfigWithTypeMissing: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/_next_no_type', + behavior: { + setOrigin: { + name: 'nameWithoutType', + // type is missing + }, + }, + }, + ], + }, + }; + + expect(() => processConfig(azionConfigWithTypeMissing)).toThrow( + "The 'name or type' field is required in the 'setOrigin' object.", + ); + }); + + it('should throw an error for an undefined property', () => { + const azionConfigWithUndefinedProperty = { + cache: [ + { + name: 'testCache', + undefinedProperty: 'This property does not exist', + }, + ], + }; + + expect(() => processConfig(azionConfigWithUndefinedProperty)).toThrow( + 'No additional properties are allowed in cache item objects.', + ); + }); + + it('should correctly process the runFunction behavior with only the required path', () => { + const azionConfigWithRunFunctionOnlyPath = { + rules: { + request: [ + { + name: 'testRule', + match: '/run-function-test-path-only', + behavior: { + runFunction: { + path: '.edge/worker.js', + }, + }, + }, + ], + }, + }; + + const expectedBehaviorPathOnly = { + target: '.edge/worker.js', + }; + + const resultPathOnly = processConfig(azionConfigWithRunFunctionOnlyPath); + expect(resultPathOnly.rules[0].behaviors).toEqual( + expect.arrayContaining([expect.objectContaining(expectedBehaviorPathOnly)]), + ); + }); + + it('should include the behavior deliver when deliver is true', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/path', + behavior: { + deliver: true, + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'deliver', + }), + ]), + ); + }); + + it('should throw an error if deliver is not a boolean', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/path', + behavior: { + deliver: 'true', // Incorrectly defined as string + }, + }, + ], + }, + }; + + expect(() => processConfig(azionConfig)).toThrow("The 'deliver' field must be a boolean or null."); + }); + + it('should throw an error if setCookie is not a string', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/', + behavior: { + setCookie: true, // Incorrectly defined as boolean + }, + }, + ], + }, + }; + + expect(() => processConfig(azionConfig)).toThrow("The 'setCookie' field must be a string or null."); + }); + + it('should throw an error if setHeaders is not an array of strings', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/', + behavior: { + setHeaders: { key: 'value' }, // Incorrectly defined as object + }, + }, + ], + }, + }; + + expect(() => processConfig(azionConfig)).toThrow("The 'setHeaders' field must be an array of strings."); + }); + + it('should correctly add deliver behavior when deliver is true', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/path', + behavior: { + deliver: true, + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'deliver', + }), + ]), + ); + }); + + it('should correctly add setCookie behavior with a valid string', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/', + behavior: { + setCookie: 'sessionId=abc123', + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'add_request_cookie', + target: 'sessionId=abc123', + }), + ]), + ); + }); + + it('should correctly add setHeaders behavior with a valid array', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/', + behavior: { + setHeaders: ['Authorization: Bearer abc123'], // Corretamente definido como uma array de strings + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'add_request_header', + target: 'Authorization: Bearer abc123', + }), + ]), + ); + }); + }); + describe('Origin', () => { + it('should process the config config when the origin is single_origin and all fields', () => { + const azionConfig: AzionConfig = { + origin: [ + { + name: 'my single origin', + type: 'single_origin', + path: '', + addresses: [ + { + address: 'http.bin.org', + }, + ], + protocolPolicy: 'preserve', + hostHeader: '${host}', + method: 'ip_hash', + redirection: true, + connectionTimeout: 60, + timeoutBetweenBytes: 120, + hmac: { + region: 'us-east-1', + accessKey: 'myaccesskey', + secretKey: 'secretKey', + }, + }, + ], + }; + const result = processConfig(azionConfig); + expect(result.origin).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'my single origin', + origin_type: 'single_origin', + addresses: [ + { + address: 'http.bin.org', + }, + ], + origin_path: '', + method: 'ip_hash', + origin_protocol_policy: 'preserve', + host_header: '${host}', + is_origin_redirection_enabled: true, + connection_timeout: 60, + timeout_between_bytes: 120, + hmac_authentication: true, + hmac_region_name: 'us-east-1', + hmac_access_key: 'myaccesskey', + hmac_secret_key: 'secretKey', + }), + ]), + ); + }); + + it('should process the config config when the origin is provided id and key', () => { + const azionConfig: any = { + origin: [ + { + id: 123456, + key: 'abcdef', + name: 'my single', + type: 'single_origin', + addresses: [ + { + address: 'http.bin.org', + }, + ], + }, + ], + }; + const result = processConfig(azionConfig); + expect(result.origin).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: 123456, + key: 'abcdef', + name: 'my single', + origin_type: 'single_origin', + addresses: [ + { + address: 'http.bin.org', + }, + ], + }), + ]), + ); + }); + + it('should throw an error when the origin type single_origin is missing the addresses field', () => { + const azionConfig: any = { + origin: [ + { + name: 'my single', + type: 'single_origin', + }, + ], + }; + expect(() => processConfig(azionConfig)).toThrow('When origin type is single_origin, addresses is required'); + }); + + // should process the config config when the origin is single_origin and addresses is array of strings + it('should process the config config when the origin is single_origin and addresses is array of strings', () => { + const azionConfig: any = { + origin: [ + { + name: 'my single', + type: 'single_origin', + addresses: ['http.bin.org', 'http2.bin.org'], + }, + ], + }; + const result = processConfig(azionConfig); + expect(result.origin).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'my single', + origin_type: 'single_origin', + addresses: [ + { + address: 'http.bin.org', + }, + { + address: 'http2.bin.org', + }, + ], + }), + ]), + ); + }); + + it('should throw an error when the origin type single_origin the addresses weight is invalid', () => { + const azionConfig: any = { + origin: [ + { + name: 'my single', + type: 'single_origin', + addresses: [ + { + address: 'http.bin.org', + weight: 1, + }, + { + address: 'http2.bin.org', + weight: 11, + }, + ], + }, + ], + }; + expect(() => processConfig(azionConfig)).toThrow( + 'When origin type is single_origin, weight must be between 0 and 10', + ); + }); + + it('should process the config config when the origin is object_storage and all fields', () => { + const azionConfig: any = { + origin: [ + { + name: 'my origin storage', + type: 'object_storage', + bucket: 'mybucket', + prefix: 'myfolder', + }, + ], + }; + const result = processConfig(azionConfig); + expect(result.origin).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'my origin storage', + origin_type: 'object_storage', + bucket: 'mybucket', + prefix: 'myfolder', + }), + ]), + ); + }); + + it('should process the config config when the origin name and type are the same as the rules setOrigin name and type', () => { + const azionConfig: any = { + origin: [ + { + name: 'my origin storage', + type: 'object_storage', + bucket: 'mybucket', + prefix: 'myfolder', + }, + ], + rules: { + request: [ + { + name: 'testRule', + match: '/api', + behavior: { + setOrigin: { + name: 'my origin storage', + type: 'object_storage', + }, + }, + }, + ], + }, + }; + + expect(() => processConfig(azionConfig)).not.toThrow(); + const result = processConfig(azionConfig); + expect(result.origin).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'my origin storage', + origin_type: 'object_storage', + bucket: 'mybucket', + prefix: 'myfolder', + }), + ]), + ); + }); + + it('should throw an error when the origin name is different from the rules setOrigin name', () => { + const azionConfig: any = { + origin: [ + { + name: 'my origin storage', + type: 'object_storage', + bucket: 'mybucket', + prefix: 'myfolder', + }, + ], + rules: { + request: [ + { + name: 'testRule', + match: '/api', + behavior: { + setOrigin: { + name: 'another origin storage', + type: 'object_storage', + }, + }, + }, + ], + }, + }; + + expect(() => processConfig(azionConfig)).toThrow( + "Rule setOrigin name 'another origin storage' not found in the origin settings", + ); + }); + + it('should throw an error when the origin type is different from the rules setOrigin type', () => { + const azionConfig: any = { + origin: [ + { + name: 'my origin storage', + type: 'object_storage', + bucket: 'mybucket', + prefix: 'myfolder', + }, + ], + rules: { + request: [ + { + name: 'testRule', + match: '/api', + behavior: { + setOrigin: { + name: 'my origin storage', + type: 'another_type', + }, + }, + }, + ], + }, + }; + + expect(() => processConfig(azionConfig)).toThrow( + "Rule setOrigin name 'my origin storage' not found in the origin settings", + ); + }); + + it('should throw an error when the origin settings are not defined', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRule', + match: '/api', + behavior: { + setOrigin: { + name: 'my origin storage', + type: 'object_storage', + }, + }, + }, + ], + }, + }; + + expect(() => processConfig(azionConfig)).toThrow( + "Rule setOrigin name 'my origin storage' not found in the origin settings", + ); + }); + + it('should throw an error when the origin type is incorrect', () => { + const azionConfig: any = { + origin: [ + { + name: 'my origin storage', + type: 'name_incorrect', + bucket: 'mybucket', + prefix: 'myfolder', + }, + ], + rules: { + request: [ + { + name: 'testRule', + match: '/api', + behavior: { + setOrigin: { + name: 'my origin storage', + type: 'another_type', + }, + }, + }, + ], + }, + }; + + expect(() => processConfig(azionConfig)).toThrow( + "The 'type' field must be a string and one of 'single_origin', 'object_storage', 'load_balancer' or 'live_ingest'.", + ); + }); + + it('should correctly process the config config when the origin is single_origin', () => { + const azionConfig: any = { + origin: [ + { + name: 'my single origin', + type: 'single_origin', + hostHeader: 'www.example.com', + addresses: [ + { + address: 'http.bin.org', + }, + ], + }, + ], + rules: { + request: [ + { + name: 'testRule', + match: '/api', + behavior: { + setOrigin: { + name: 'my single origin', + type: 'single_origin', + }, + }, + }, + ], + }, + }; + expect(() => processConfig(azionConfig)).not.toThrow(); + const result = processConfig(azionConfig); + expect(result.origin).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'my single origin', + origin_type: 'single_origin', + addresses: [ + { + address: 'http.bin.org', + }, + ], + host_header: 'www.example.com', + }), + ]), + ); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'set_origin', + target: 'my single origin', + }), + ]), + ); + }); + + it('should throw an error when the origin path is "/"', () => { + const azionConfig: any = { + origin: [ + { + name: 'my single', + type: 'single_origin', + addresses: ['http.bin.org', 'http2.bin.org'], + path: '/', + }, + ], + }; + expect(() => processConfig(azionConfig)).toThrow( + 'Origin path cannot be "/". Please use empty string or "/path"', + ); + }); + }); + describe('Rules', () => { + it('should correctly handle bypassCache behavior', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testBypassCache', + match: '/', + behavior: { + bypassCache: true, + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'bypass_cache_phase', + target: null, + }), + ]), + ); + }); + + it('should correctly handle redirect to 301', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRedirect301', + match: '/', + behavior: { + redirectTo301: 'https://example.com', + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'redirect_to_301', + target: 'https://example.com', + }), + ]), + ); + }); + + it('should correctly handle redirect to 302', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testRedirect302', + match: '/', + behavior: { + redirectTo302: 'https://example.com', + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'redirect_to_302', + target: 'https://example.com', + }), + ]), + ); + }); + + it('should correctly handle capture match groups', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'testCapture', + match: '/', + behavior: { + capture: { + match: '^/user/(.*)', + captured: 'userId', + subject: 'uri', + }, + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'capture_match_groups', + target: { + regex: '^/user/(.*)', + captured_array: 'userId', + // eslint-disable-next-line no-template-curly-in-string + subject: '${uri}', + }, + }), + ]), + ); + }); + + it('should correctly handle filterCookie behavior', () => { + const azionConfig: any = { + rules: { + response: [ + { + name: 'testFilterCookie', + match: '/', + behavior: { + filterCookie: '_cookie', + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'filter_response_cookie', + target: '_cookie', + }), + ]), + ); + }); + + it('should correctly process rules in the response phase', () => { + const azionConfig: any = { + rules: { + response: [ + { + name: 'testResponsePhase', + match: '/', + behavior: { + setHeaders: ['X-Test-Header: value'], + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'add_response_header', + target: 'X-Test-Header: value', + }), + ]), + ); + }); + + it('should correctly add multiple response headers', () => { + const azionConfig: any = { + rules: { + response: [ + { + name: 'testMultipleHeaders', + match: '/', + behavior: { + setHeaders: ['X-Frame-Options: DENY', "Content-Security-Policy: default-src 'self'"], + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'add_response_header', + target: 'X-Frame-Options: DENY', + }), + expect.objectContaining({ + name: 'add_response_header', + target: "Content-Security-Policy: default-src 'self'", + }), + ]), + ); + }); + + it('should correctly handle enableGZIP behavior', () => { + const azionConfig: any = { + rules: { + response: [ + { + name: 'testEnableGZIP', + match: '/', + behavior: { + enableGZIP: true, + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'enable_gzip', + target: '', + }), + ]), + ); + }); + + it('should handle rules with description and active properties correctly', () => { + const azionConfig: any = { + rules: { + request: [ + { + name: 'Example Rule', + match: '/', + description: 'This rule redirects all traffic.', + active: false, + }, + { + name: 'Second Rule', + match: '/api', + behavior: {}, + // description is not provided here + active: true, + }, + { + name: 'Third Rule', + match: '/home', + description: 'This rule handles home traffic.', + behavior: {}, + // active is not provided here + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules).toEqual([ + expect.objectContaining({ + name: 'Example Rule', + description: 'This rule redirects all traffic.', + is_active: false, + }), + expect.objectContaining({ + name: 'Second Rule', + description: '', // Should default to an empty string + is_active: true, + }), + expect.objectContaining({ + name: 'Third Rule', + description: 'This rule handles home traffic.', + is_active: true, // Should default to true + }), + ]); + }); + + it('should correctly assign order starting from 2 for request and response rules', () => { + const azionConfig: any = { + rules: { + request: [ + { name: 'First Request Rule', match: '/', behavior: {} }, + { name: 'Second Request Rule', match: '/second', behavior: {} }, + ], + response: [ + { name: 'First Response Rule', match: '/', behavior: {} }, + { name: 'Second Response Rule', match: '/second', behavior: {} }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0]).toEqual( + expect.objectContaining({ + name: 'First Request Rule', + order: 2, + }), + ); + expect(result.rules[1]).toEqual( + expect.objectContaining({ + name: 'Second Request Rule', + order: 3, + }), + ); + expect(result.rules[2]).toEqual( + expect.objectContaining({ + name: 'First Response Rule', + order: 2, + }), + ); + expect(result.rules[3]).toEqual( + expect.objectContaining({ + name: 'Second Response Rule', + order: 3, + }), + ); + }); + + it('should maintain the order of behaviors as specified by the user', () => { + const azionConfig: any = { + origin: [ + { + name: 'my origin storage', + type: 'object_storage', + bucket: 'mybucket', + prefix: 'myfolder', + }, + ], + rules: { + request: [ + { + name: 'testRule', + match: '/', + behavior: { + setHeaders: ['Authorization: Bearer abc123'], + deliver: true, + setOrigin: { + name: 'my origin storage', + type: 'object_storage', + }, + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual([ + expect.objectContaining({ + name: 'add_request_header', + target: 'Authorization: Bearer abc123', + }), + expect.objectContaining({ + name: 'deliver', + }), + expect.objectContaining({ + name: 'set_origin', + target: 'my origin storage', + }), + ]); + }); + it('should throw an error when the origin settings are not defined', () => { + const azionConfigWithoutOrigin = { + rules: { + request: [ + { + name: 'testRule', + match: '/api', + behavior: { + setOrigin: { + name: 'undefined origin', + type: 'object_storage', + }, + }, + }, + ], + }, + }; + + expect(() => processConfig(azionConfigWithoutOrigin)).toThrow( + "Rule setOrigin name 'undefined origin' not found in the origin settings", + ); + }); + it('should handle legacy config without behavior field for request rules', () => { + const azionConfig: any = { + origin: [ + { + name: 'legacy origin', + type: 'object_storage', + }, + ], + rules: { + request: [ + { + name: 'legacyRule', + match: '/legacy', + setOrigin: { + name: 'legacy origin', + type: 'object_storage', + }, + setHeaders: ['Authorization: Bearer legacy'], + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'set_origin', + target: 'legacy origin', + }), + expect.objectContaining({ + name: 'add_request_header', + target: 'Authorization: Bearer legacy', + }), + ]), + ); + }); + + it('should handle legacy config without behavior field for response rules', () => { + const azionConfig: any = { + rules: { + response: [ + { + name: 'legacyResponseRule', + match: '/legacy-response', + setHeaders: ['X-Legacy-Header: legacy'], + enableGZIP: true, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'add_response_header', + target: 'X-Legacy-Header: legacy', + }), + expect.objectContaining({ + name: 'enable_gzip', + target: '', + }), + ]), + ); + }); + + it('should handle mixed legacy and new config for request rules', () => { + const azionConfig: any = { + origin: [ + { + name: 'mixed origin', + type: 'object_storage', + }, + ], + rules: { + request: [ + { + name: 'mixedRule', + match: '/mixed', + setOrigin: { + name: 'mixed origin', + type: 'object_storage', + }, + behavior: { + setHeaders: ['Authorization: Bearer mixed'], + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'set_origin', + target: 'mixed origin', + }), + expect.objectContaining({ + name: 'add_request_header', + target: 'Authorization: Bearer mixed', + }), + ]), + ); + }); + + it('should handle mixed legacy and new config for response rules', () => { + const azionConfig: any = { + rules: { + response: [ + { + name: 'mixedResponseRule', + match: '/mixed-response', + setHeaders: ['X-Mixed-Header: mixed'], + behavior: { + enableGZIP: true, + }, + }, + ], + }, + }; + + const result = processConfig(azionConfig); + expect(result.rules[0].behaviors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'add_response_header', + target: 'X-Mixed-Header: mixed', + }), + expect.objectContaining({ + name: 'enable_gzip', + target: '', + }), + ]), + ); + }); + it('should correctly process cacheByQueryString with option "ignore"', () => { + const azionConfig: any = { + cache: [ + { + name: 'testCache', + cacheByQueryString: { + option: 'ignore', + }, + }, + ], + rules: { + request: [], + }, + }; + + const result = processConfig(azionConfig); + expect(result.cache[0]).toEqual( + expect.objectContaining({ + cache_by_query_string: 'ignore', + query_string_fields: [], + }), + ); + }); + + it('should correctly process cacheByQueryString with option "whitelist" and list', () => { + const azionConfig: any = { + cache: [ + { + name: 'testCache', + cacheByQueryString: { + option: 'whitelist', + list: ['param1', 'param2'], + }, + }, + ], + rules: { + request: [], + }, + }; + + const result = processConfig(azionConfig); + expect(result.cache[0]).toEqual( + expect.objectContaining({ + cache_by_query_string: 'whitelist', + query_string_fields: ['param1', 'param2'], + }), + ); + }); + + it('should throw an error if cacheByQueryString option is "whitelist" or "blacklist" without list', () => { + const azionConfig: any = { + cache: [ + { + name: 'testCache', + cacheByQueryString: { + option: 'whitelist', + }, + }, + ], + rules: { + request: [], + }, + }; + + expect(() => processConfig(azionConfig)).toThrow( + "The 'list' field is required when 'option' is 'whitelist' or 'blacklist'.", + ); + }); + + it('should correctly process cacheByQueryString with option "blacklist" and list', () => { + const azionConfig: any = { + cache: [ + { + name: 'testCache', + cacheByQueryString: { + option: 'blacklist', + list: ['param1', 'param2'], + }, + }, + ], + rules: { + request: [], + }, + }; + + const result = processConfig(azionConfig); + expect(result.cache[0]).toEqual( + expect.objectContaining({ + cache_by_query_string: 'blacklist', + query_string_fields: ['param1', 'param2'], + }), + ); + }); + + it('should correctly process cacheByQueryString with option "all"', () => { + const azionConfig: any = { + cache: [ + { + name: 'testCache', + cacheByQueryString: { + option: 'varies', + }, + }, + ], + rules: { + request: [], + }, + }; + + const result = processConfig(azionConfig); + expect(result.cache[0]).toEqual( + expect.objectContaining({ + cache_by_query_string: 'all', + query_string_fields: [], + }), + ); + }); + it('should correctly process cacheByCookie with option "ignore"', () => { + const azionConfig: any = { + cache: [ + { + name: 'testCache', + cacheByCookie: { + option: 'ignore', + }, + }, + ], + rules: { + request: [], + }, + }; + + const result = processConfig(azionConfig); + expect(result.cache[0]).toEqual( + expect.objectContaining({ + cache_by_cookie: 'ignore', + cookie_names: [], + }), + ); + }); + + it('should correctly process cacheByCookie with option "whitelist" and list', () => { + const azionConfig: any = { + cache: [ + { + name: 'testCache', + cacheByCookie: { + option: 'whitelist', + list: ['cookie1', 'cookie2'], + }, + }, + ], + rules: { + request: [], + }, + }; + + const result = processConfig(azionConfig); + expect(result.cache[0]).toEqual( + expect.objectContaining({ + cache_by_cookie: 'whitelist', + cookie_names: ['cookie1', 'cookie2'], + }), + ); + }); + + it('should throw an error if cacheByCookie option is "whitelist" or "blacklist" without list', () => { + const azionConfig: any = { + cache: [ + { + name: 'testCache', + cacheByCookie: { + option: 'whitelist', + }, + }, + ], + rules: { + request: [], + }, + }; + + expect(() => processConfig(azionConfig)).toThrow( + "The 'list' field is required when 'option' is 'whitelist' or 'blacklist'.", + ); + }); + + it('should correctly process cacheByCookie with option "blacklist" and list', () => { + const azionConfig: any = { + cache: [ + { + name: 'testCache', + cacheByCookie: { + option: 'blacklist', + list: ['cookie1', 'cookie2'], + }, + }, + ], + rules: { + request: [], + }, + }; + + const result = processConfig(azionConfig); + expect(result.cache[0]).toEqual( + expect.objectContaining({ + cache_by_cookie: 'blacklist', + cookie_names: ['cookie1', 'cookie2'], + }), + ); + }); + + it('should correctly process cacheByCookie with option "all"', () => { + const azionConfig: any = { + cache: [ + { + name: 'testCache', + cacheByCookie: { + option: 'varies', + }, + }, + ], + rules: { + request: [], + }, + }; + + const result = processConfig(azionConfig); + expect(result.cache[0]).toEqual( + expect.objectContaining({ + cache_by_cookie: 'all', + cookie_names: [], + }), + ); + }); + }); + describe('Domain', () => { + it('should throw process the config config when the domain name is not provided', () => { + const azionConfig: any = { + domain: { + cnames: ['www.example.com'], + }, + }; + + expect(() => processConfig(azionConfig)).toThrow("The 'name' field is required in the domain object."); + }); + + it('should correctly process the config config when the domain cnameAccessOnly undefined', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + cnames: ['www.example.com'], + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + cname_access_only: false, + cnames: ['www.example.com'], + }), + ); + }); + + it('should correctly process the config config when the domain cnameAccessOnly is true', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + cnames: ['www.example.com'], + cnameAccessOnly: true, + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + cname_access_only: true, + cnames: ['www.example.com'], + }), + ); + }); + + it('should throw process the config config when the domain cnames is not an array', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + cnames: 'www.example.com', + }, + }; + + expect(() => processConfig(azionConfig)).toThrow("The 'cnames' field must be an array of strings."); + }); + + it('should throw process the config config when the domain digitalCertificateId different from lets_encrypt', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + digitalCertificateId: 'mycert', + }, + }; + + expect(() => processConfig(azionConfig)).toThrow( + "Domain mydomain has an invalid digital certificate ID: mycert. Only 'lets_encrypt' or null is supported.", + ); + }); + + it('should correctly process the config config when the domain digitalCertificateId is null', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + digitalCertificateId: null, + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + digital_certificate_id: null, + }), + ); + }); + + it('should correctly process the config config when the domain digitalCertificateId is lets_encrypt', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + digitalCertificateId: 'lets_encrypt', + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + digital_certificate_id: 'lets_encrypt', + }), + ); + }); + + it('should correctly process the config config when the domain digitalCertificateId is number', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + digitalCertificateId: 12345, + }, + }; + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + digital_certificate_id: 12345, + }), + ); + }); + + it('should correctly process the config config when the domain digitalCertificateId is not provided', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + digital_certificate_id: null, + }), + ); + }); + + it('should correctly process the config config when the domain mtls is not provided', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + }, + }; + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + is_mtls_enabled: false, + }), + ); + }); + + it('should correctly process the config config when the domain mtls is active and verification equal enforce', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + mtls: { + verification: 'enforce', + trustedCaCertificateId: 12345, + }, + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + is_mtls_enabled: true, + mtls_verification: 'enforce', + mtls_trusted_ca_certificate_id: 12345, + }), + ); + }); + + it('should correctly process the config config when the domain mtls is active and verification equal permissive', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + mtls: { + verification: 'permissive', + trustedCaCertificateId: 12345, + }, + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + is_mtls_enabled: true, + mtls_verification: 'permissive', + mtls_trusted_ca_certificate_id: 12345, + }), + ); + }); + + it('should throw an error when the domain verification is not provided', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + mtls: { + trustedCaCertificateId: 12345, + }, + }, + }; + + expect(() => processConfig(azionConfig)).toThrow( + "The 'verification and trustedCaCertificateId' fields are required in the mtls object.", + ); + }); + + it('should throw an error when the domain trustedCaCertificateId is not provided', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + mtls: { + verification: 'enforce', + }, + }, + }; + + expect(() => processConfig(azionConfig)).toThrow( + "The 'verification and trustedCaCertificateId' fields are required in the mtls object.", + ); + }); + + it('should correctly process the config config when the domain mtls and crlList is present', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + mtls: { + verification: 'enforce', + trustedCaCertificateId: 12345, + crlList: [123, 456], + }, + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + is_mtls_enabled: true, + mtls_verification: 'enforce', + mtls_trusted_ca_certificate_id: 12345, + crl_list: [123, 456], + }), + ); + }); + + it('should correctly process the config config when the domain edgeApplicationId is provided', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + edgeApplicationId: 12345, + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + edge_application_id: 12345, + }), + ); + }); + + it('should throw an error when the domain edgeApplicationId is not a number', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + edgeApplicationId: '12345', + }, + }; + + expect(() => processConfig(azionConfig)).toThrow("The 'edgeApplicationId' field must be a number."); + }); + + it('should correctly process the config config when the domain edgeApplicationId is not provided', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + edgeApplicationId: 0, + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + edge_application_id: null, + }), + ); + }); + + it('should correctly process the config config when the domain edgeFirewallId is provided', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + edgeFirewallId: 12345, + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + edge_firewall_id: 12345, + }), + ); + }); + + it('should throw an error when the domain edgeFirewallId is not a number', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + edgeFirewallId: '12345', + }, + }; + + expect(() => processConfig(azionConfig)).toThrow("The 'edgeFirewallId' field must be a number."); + }); + + it('should correctly process the config config when the domain edgeFirewallId is not provided', () => { + const azionConfig: any = { + domain: { + name: 'mydomain', + }, + }; + + const result = processConfig(azionConfig); + expect(result.domain).toEqual( + expect.objectContaining({ + name: 'mydomain', + edge_firewall_id: null, + }), + ); + }); + }); + describe('Purge', () => { + it('should correctly process the config config when the purge is type url', () => { + const azionConfig: any = { + purge: [ + { + type: 'url', + urls: ['https://example.com'], + }, + ], + }; + + const result = processConfig(azionConfig); + expect(result.purge).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'url', + urls: ['https://example.com'], + method: 'delete', + }), + ]), + ); + }); + + it('should correctly process the config config when the purge is type url and method is provided', () => { + const azionConfig: any = { + purge: [ + { + type: 'url', + urls: ['https://example.com'], + method: 'delete', + }, + ], + }; + + const result = processConfig(azionConfig); + expect(result.purge).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'url', + urls: ['https://example.com'], + method: 'delete', + }), + ]), + ); + }); + + it('should throw an error when the purge is method is invalid', () => { + const azionConfig: any = { + purge: [ + { + type: 'url', + urls: ['https://example.com'], + method: 'invalid', + }, + ], + }; + + expect(() => processConfig(azionConfig)).toThrow( + "The 'method' field must be either 'delete'. Default is 'delete'.", + ); + }); + + it('should throw an error when the purge is type is invalid', () => { + const azionConfig: any = { + purge: [ + { + type: 'invalid', + urls: ['https://example.com'], + }, + ], + }; + + expect(() => processConfig(azionConfig)).toThrow( + "The 'type' field must be either 'url', 'cachekey' or 'wildcard'.", + ); + }); + + it('should correctly process the config config when the purge is type cachekey', () => { + const azionConfig: any = { + purge: [ + { + type: 'cachekey', + urls: ['https://example.com/test1', 'https://example.com/test2'], + }, + ], + }; + + const result = processConfig(azionConfig); + expect(result.purge).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'cachekey', + urls: ['https://example.com/test1', 'https://example.com/test2'], + method: 'delete', + }), + ]), + ); + }); + + it('should correctly process the config config when the purge is type cachekey and layer is provided', () => { + const azionConfig: any = { + purge: [ + { + type: 'cachekey', + urls: ['https://example.com/test1', 'https://example.com/test2'], + layer: 'edge_caching', + }, + ], + }; + + const result = processConfig(azionConfig); + expect(result.purge).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'cachekey', + urls: ['https://example.com/test1', 'https://example.com/test2'], + method: 'delete', + layer: 'edge_caching', + }), + ]), + ); + }); + + it('should throw an error when the purge is type cachekey and layer is invalid', () => { + const azionConfig: any = { + purge: [ + { + type: 'cachekey', + urls: ['https://example.com/test'], + layer: 'invalid', + }, + ], + }; + + expect(() => processConfig(azionConfig)).toThrow( + "The 'layer' field must be either 'edge_caching' or 'l2_caching'. Default is 'edge_caching'.", + ); + }); + + it('should correctly process the config config when the purge is type wildcard', () => { + const azionConfig: AzionConfig = { + purge: [ + { + type: 'wildcard', + urls: ['https://example.com/*'], + }, + ], + }; + + const result = processConfig(azionConfig); + expect(result.purge).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'wildcard', + urls: ['https://example.com/*'], + method: 'delete', + }), + ]), + ); + }); + + it('should throw an error when the purge urls is not an array', () => { + const azionConfig: any = { + purge: [ + { + type: 'url', + urls: 'https://example.com', + }, + ], + }; + + expect(() => processConfig(azionConfig)).toThrow("The 'urls' field must be an array of strings."); + }); + }); + describe('Build', () => { + it('should correctly process the config config when the build is type build', () => { + const azionConfig: AzionConfig = { + build: { + builder: 'esbuild', + preset: { + name: 'react', + }, + }, + }; + + const result = processConfig(azionConfig); + expect(result.build).toEqual( + expect.objectContaining({ + builder: 'esbuild', + preset: { + name: 'react', + }, + }), + ); + }); + }); + }); +}); diff --git a/packages/config/src/processConfig/index.ts b/packages/config/src/processConfig/index.ts new file mode 100644 index 0000000..ea4384d --- /dev/null +++ b/packages/config/src/processConfig/index.ts @@ -0,0 +1,86 @@ +import Ajv from 'ajv'; +import ajvErrors from 'ajv-errors'; +import addKeywords from 'ajv-keywords'; + +import { AzionConfig } from '../types'; +import convertLegacyConfig from './helpers/convertLegacyConfig'; +import azionConfigSchema from './helpers/schema'; +import BuildProcessConfigStrategy from './strategy/implementations/buildProcessConfigStrategy'; +import CacheProcessConfigStrategy from './strategy/implementations/cacheProcessConfigStrategy'; +import DomainProcessConfigStrategy from './strategy/implementations/domainProcessConfigStrategy'; +import OriginProcessConfigStrategy from './strategy/implementations/originProcessConfigStrategy'; +import PurgeProcessConfigStrategy from './strategy/implementations/purgeProcessConfigStrategy'; +import RulesProcessConfigStrategy from './strategy/implementations/rulesProcessConfigStrategy'; +import ProcessConfigContext from './strategy/processConfigContext'; + +/** + * Validates the provided configuration against a JSON Schema. + * This function uses AJV (Another JSON Schema Validator) to validate the configuration. + * If the configuration is not valid, an exception is thrown with the error message of the first validation issue encountered. + * @param {object} config - The configuration object to be validated. + * @throws {Error} Throws an error if the configuration fails validation. + */ +function validateConfig(config: AzionConfig) { + const ajv = new Ajv({ allErrors: true, $data: true, allowUnionTypes: true }); + ajvErrors(ajv); + addKeywords(ajv, ['instanceof']); + const validate = ajv.compile(azionConfigSchema); + const valid = validate(config); + + if (!valid) { + if (validate.errors && validate.errors.length > 0) { + throw new Error(validate.errors[0].message); + } else { + throw new Error('Configuration validation failed.'); + } + } +} + +/** + * Processes the provided configuration object and returns a JSON object that can be used to create or update an Azion CDN configuration. + * @param inputConfig AzionConfig + * @returns + * + * @example + * const config = { + * origin: [ + * { + * name: 'My Origin', + * type: 'single_origin', + * addresses: [ + * { + * address: 'origin.example.com', + * weight: 100, + * }, + * ], + * protocolPolicy: 'https', + * }, + * ], + * } + * const payloadCDN = processConfig(config); + * console.log(payloadCDN); + */ +function processConfig(inputConfig: AzionConfig) { + /* Converts legacy configuration properties to the new `behavior` format. */ + const config = convertLegacyConfig(inputConfig); + validateConfig(config); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payloadCDN: any = {}; + + // ProcessConfig Strategy Pattern + const processConfigContext = new ProcessConfigContext(); + processConfigContext.setStrategy('build', new BuildProcessConfigStrategy()); + processConfigContext.setStrategy('origin', new OriginProcessConfigStrategy()); + processConfigContext.setStrategy('cache', new CacheProcessConfigStrategy()); + processConfigContext.setStrategy('domain', new DomainProcessConfigStrategy()); + processConfigContext.setStrategy('purge', new PurgeProcessConfigStrategy()); + + // Rules must be last to apply to behaviors (origin, cache...) + processConfigContext.setStrategy('rules', new RulesProcessConfigStrategy()); + processConfigContext.generate(config, payloadCDN); + + return payloadCDN; +} + +export { processConfig, validateConfig }; diff --git a/packages/config/src/processConfig/strategy/implementations/buildProcessConfigStrategy.ts b/packages/config/src/processConfig/strategy/implementations/buildProcessConfigStrategy.ts new file mode 100644 index 0000000..5a2bfea --- /dev/null +++ b/packages/config/src/processConfig/strategy/implementations/buildProcessConfigStrategy.ts @@ -0,0 +1,20 @@ +import { AzionBuild, AzionConfig } from '../../../types'; +import ProcessConfigStrategy from '../processConfigStrategy'; + +/** + * BuildProcessConfigStrategy + * @class BuildProcessConfigStrategy + * @description This class is implementation of the Build Process Config Strategy. + */ +class BuildProcessConfigStrategy extends ProcessConfigStrategy { + generate(config: AzionConfig) { + const build = config?.build; + if (!build) { + return {}; + } + const payload: AzionBuild = build; + return payload; + } +} + +export default BuildProcessConfigStrategy; diff --git a/packages/config/src/processConfig/strategy/implementations/cacheProcessConfigStrategy.ts b/packages/config/src/processConfig/strategy/implementations/cacheProcessConfigStrategy.ts new file mode 100644 index 0000000..7db8dea --- /dev/null +++ b/packages/config/src/processConfig/strategy/implementations/cacheProcessConfigStrategy.ts @@ -0,0 +1,70 @@ +import { evaluate } from 'mathjs'; +import { AzionConfig } from '../../../types'; +import ProcessConfigStrategy from '../processConfigStrategy'; + +/** + * CacheProcessConfigStrategy + * @class CacheProcessConfigStrategy + * @description This class is implementation of the Cache ProcessConfig Strategy. + */ +class CacheProcessConfigStrategy extends ProcessConfigStrategy { + generate(config: AzionConfig) { + // Helper function to safely evaluate mathematical expressions + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const evaluateMathExpression = (expression: any) => { + if (typeof expression === 'number') { + return expression; + } + if (/^[0-9+\-*/.() ]+$/.test(expression)) { + return evaluate(expression); + } + throw new Error(`Expression is not purely mathematical: ${expression}`); + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any[] = []; + if (!Array.isArray(config?.cache) || config?.cache.length === 0) { + return payload; + } + config?.cache.forEach((cache) => { + const maxAgeSecondsBrowser = cache?.browser ? evaluateMathExpression(cache.browser.maxAgeSeconds) : 0; + const maxAgeSecondsEdge = cache?.edge ? evaluateMathExpression(cache.edge.maxAgeSeconds) : 60; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const cacheSetting: any = { + name: cache.name, + browser_cache_settings: cache?.browser ? 'override' : 'honor', + browser_cache_settings_maximum_ttl: maxAgeSecondsBrowser, + cdn_cache_settings: cache?.edge ? 'override' : 'honor', + cdn_cache_settings_maximum_ttl: maxAgeSecondsEdge, + enable_caching_for_post: cache?.methods?.post || false, + enable_caching_for_options: cache?.methods?.options || false, + enable_query_string_sort: cache?.queryStringSort || false, + }; + + if (cache.cacheByQueryString) { + cacheSetting.cache_by_query_string = + cache.cacheByQueryString.option === 'varies' ? 'all' : cache.cacheByQueryString.option; + if (cache.cacheByQueryString.option === 'whitelist' || cache.cacheByQueryString.option === 'blacklist') { + cacheSetting.query_string_fields = cache.cacheByQueryString.list || []; + } else { + cacheSetting.query_string_fields = []; + } + } + + if (cache.cacheByCookie) { + cacheSetting.cache_by_cookie = cache.cacheByCookie.option === 'varies' ? 'all' : cache.cacheByCookie.option; + if (cache.cacheByCookie.option === 'whitelist' || cache.cacheByCookie.option === 'blacklist') { + cacheSetting.cookie_names = cache.cacheByCookie.list || []; + } else { + cacheSetting.cookie_names = []; + } + } + + payload.push(cacheSetting); + }); + return payload; + } +} + +export default CacheProcessConfigStrategy; diff --git a/packages/config/src/processConfig/strategy/implementations/domainProcessConfigStrategy.ts b/packages/config/src/processConfig/strategy/implementations/domainProcessConfigStrategy.ts new file mode 100644 index 0000000..b5bb58f --- /dev/null +++ b/packages/config/src/processConfig/strategy/implementations/domainProcessConfigStrategy.ts @@ -0,0 +1,57 @@ +import { AzionConfig } from '../../../types'; +import ProcessConfigStrategy from '../processConfigStrategy'; + +/** + * DomainProcessConfigStrategy + * @class DomainProcessConfigStrategy + * @description This class is implementation of the Domain ProcessConfig Strategy. + */ +class DomainProcessConfigStrategy extends ProcessConfigStrategy { + generate(config: AzionConfig) { + const domain = config?.domain; + if (!domain) { + return {}; + } + if ( + domain.digitalCertificateId && + typeof domain.digitalCertificateId === 'string' && + domain.digitalCertificateId !== 'lets_encrypt' + ) { + throw new Error( + `Domain ${domain.name} has an invalid digital certificate ID: ${domain.digitalCertificateId}. Only 'lets_encrypt' or null is supported.`, + ); + } + + if ( + domain.mtls?.verification && + domain.mtls.verification !== 'enforce' && + domain.mtls.verification !== 'permissive' + ) { + throw new Error( + `Domain ${domain.name} has an invalid verification value: ${domain.mtls.verification}. Only 'enforce' or 'permissive' is supported.`, + ); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const domainSetting: any = { + name: domain.name, + cname_access_only: domain.cnameAccessOnly || false, + cnames: domain.cnames || [], + digital_certificate_id: domain.digitalCertificateId || null, + edge_application_id: domain.edgeApplicationId || null, + edge_firewall_id: domain.edgeFirewallId || null, + active: true, + }; + if (domain.mtls) { + domainSetting.is_mtls_enabled = true; + domainSetting.mtls_verification = domain.mtls.verification; + domainSetting.mtls_trusted_ca_certificate_id = domain.mtls.trustedCaCertificateId; + domainSetting.crl_list = domain.mtls.crlList || []; + } else { + domainSetting.is_mtls_enabled = false; + } + return domainSetting; + } +} + +export default DomainProcessConfigStrategy; diff --git a/packages/config/src/processConfig/strategy/implementations/originProcessConfigStrategy.ts b/packages/config/src/processConfig/strategy/implementations/originProcessConfigStrategy.ts new file mode 100644 index 0000000..15ff805 --- /dev/null +++ b/packages/config/src/processConfig/strategy/implementations/originProcessConfigStrategy.ts @@ -0,0 +1,78 @@ +import { AzionConfig, AzionOrigin } from '../../../types'; +import ProcessConfigStrategy from '../processConfigStrategy'; + +/** + * OriginProcessConfigStrategy + * @class OriginProcessConfigStrategy + * @description This class is implementation of the Origin ProcessConfig Strategy. + */ +class OriginProcessConfigStrategy extends ProcessConfigStrategy { + generate(config: AzionConfig) { + const payload: AzionOrigin[] = []; + if (!Array.isArray(config?.origin) || config?.origin.length === 0) { + return payload; + } + const originsType = ['single_origin', 'object_storage', 'load_balancer', 'live_ingest']; + config?.origin.forEach((origin) => { + if (originsType.indexOf(origin.type) === -1) { + throw new Error(`Rule setOrigin originType '${origin.type}' is not supported`); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const originSetting: any = { + id: origin.id, + key: origin.key, + name: origin.name, + origin_type: origin.type, + }; + + if (origin.type !== 'object_storage') { + if (origin.path === '/') { + throw new Error('Origin path cannot be "/". Please use empty string or "/path"'); + } + originSetting.origin_path = origin.path || ''; + originSetting.origin_protocol_policy = origin.protocolPolicy || 'preserve'; + originSetting.method = origin.method || 'ip_hash'; + originSetting.is_origin_redirection_enabled = origin.redirection || false; + originSetting.connection_timeout = origin.connectionTimeout || 60; + originSetting.timeout_between_bytes = origin.timeoutBetweenBytes || 120; + + if (origin.addresses && origin.addresses.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const addresses: any[] = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + origin?.addresses.forEach((address: any) => { + if (typeof address === 'string') { + addresses.push({ + address, + }); + return; + } + if (address?.weight < 0 || address?.weight > 10) { + throw new Error(`When origin type is ${origin.type}, weight must be between 0 and 10`); + } + addresses.push(address); + }); + originSetting.addresses = addresses; + } else { + throw new Error(`When origin type is ${origin.type}, addresses is required`); + } + + originSetting.host_header = origin.hostHeader || '${host}'; + if (origin?.hmac) { + originSetting.hmac_authentication = true; + originSetting.hmac_region_name = origin.hmac?.region; + originSetting.hmac_access_key = origin.hmac?.accessKey; + originSetting.hmac_secret_key = origin.hmac?.secretKey; + } + } else if (origin.type === 'object_storage') { + originSetting.bucket = origin.bucket; + originSetting.prefix = origin.prefix || ''; + } + + payload.push(originSetting); + }); + return payload; + } +} + +export default OriginProcessConfigStrategy; diff --git a/packages/config/src/processConfig/strategy/implementations/purgeProcessConfigStrategy.ts b/packages/config/src/processConfig/strategy/implementations/purgeProcessConfigStrategy.ts new file mode 100644 index 0000000..286a6c2 --- /dev/null +++ b/packages/config/src/processConfig/strategy/implementations/purgeProcessConfigStrategy.ts @@ -0,0 +1,44 @@ +import { AzionConfig } from '../../../types'; +import ProcessConfigStrategy from '../processConfigStrategy'; + +/** + * PurgeProcessConfigStrategy + * @class PurgeProcessConfigStrategy + * @description This class is implementation of the Purge ProcessConfig Strategy. + */ +class PurgeProcessConfigStrategy extends ProcessConfigStrategy { + generate(config: AzionConfig) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any[] = []; + if (!Array.isArray(config?.purge) || config?.purge.length === 0) { + return payload; + } + config?.purge.forEach((purge) => { + purge?.urls.forEach((value) => { + if (!value.includes('http://') && !value.includes('https://')) { + throw new Error('The URL must contain the protocol (http:// or https://).'); + } + + if (purge?.type === 'wildcard' && !value.includes('*')) { + throw new Error('The URL must not contain the wildcard character (*).'); + } + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const purgeSetting: any = { + type: purge.type, + urls: purge.urls || [], + method: purge.method || 'delete', + }; + + if (purge?.type === 'cachekey') { + purgeSetting.layer = purge.layer || 'edge_caching'; + } + + payload.push(purgeSetting); + }); + return payload; + } +} + +export default PurgeProcessConfigStrategy; diff --git a/packages/config/src/processConfig/strategy/implementations/rulesProcessConfigStrategy.ts b/packages/config/src/processConfig/strategy/implementations/rulesProcessConfigStrategy.ts new file mode 100644 index 0000000..6d1df72 --- /dev/null +++ b/packages/config/src/processConfig/strategy/implementations/rulesProcessConfigStrategy.ts @@ -0,0 +1,97 @@ +import { AzionConfig } from '../../../types'; +import { requestBehaviors, responseBehaviors } from '../../helpers/behaviors'; +import ProcessConfigStrategy from '../processConfigStrategy'; + +/** + * RulesProcessConfigStrategy + * @class RulesProcessConfigStrategy + * @description This class is implementation of the Rules ProcessConfig Strategy. + */ +class RulesProcessConfigStrategy extends ProcessConfigStrategy { + /** + * Adds behaviors to the CDN rule. + * @param cdnRule - The CDN rule. + * @param behaviors - The behaviors. + * @param behaviorDefinitions - The behavior definitions. + * @param payloadContext - The payload context. + * @returns + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private addBehaviors(cdnRule: any, behaviors: any, behaviorDefinitions: any, payloadContext: any) { + if (behaviors && typeof behaviors === 'object') { + Object.entries(behaviors).forEach(([key, value]) => { + if (behaviorDefinitions[key]) { + const transformedBehavior = behaviorDefinitions[key].transform(value, payloadContext); + if (Array.isArray(transformedBehavior)) { + cdnRule.behaviors.push(...transformedBehavior); + } else if (transformedBehavior) { + cdnRule.behaviors.push(transformedBehavior); + } + } else { + console.warn(`Unknown behavior: ${key}`); + } + }); + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + generate(config: AzionConfig, context: any) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const payload: any[] = []; + // request + if (Array.isArray(config?.rules?.request)) { + config?.rules?.request?.forEach((rule, index) => { + const cdnRule = { + name: rule.name, + phase: 'request', + description: rule.description ?? '', + is_active: rule.active !== undefined ? rule.active : true, // Default to true if not provided + order: index + 2, // index starts at 2, because the default rule is index 1 + criteria: [ + [ + { + variable: `\${${rule.variable ?? 'uri'}}`, + operator: 'matches', + conditional: 'if', + input_value: rule.match, + }, + ], + ], + behaviors: [], + }; + this.addBehaviors(cdnRule, rule.behavior, requestBehaviors, context); + payload.push(cdnRule); + }); + } + + // response + if (Array.isArray(config?.rules?.response)) { + config?.rules?.response.forEach((rule, index) => { + const cdnRule = { + name: rule.name, + phase: 'response', + description: rule.description ?? '', + is_active: rule.active !== undefined ? rule.active : true, // Default to true if not provided + order: index + 2, // index starts at 2, because the default rule is index 1 + criteria: [ + [ + { + variable: `\${${rule.variable ?? 'uri'}}`, + operator: 'matches', + conditional: 'if', + input_value: rule.match, + }, + ], + ], + behaviors: [], + }; + this.addBehaviors(cdnRule, rule.behavior, responseBehaviors, context); + payload.push(cdnRule); + }); + } + + return payload; + } +} + +export default RulesProcessConfigStrategy; diff --git a/packages/config/src/processConfig/strategy/processConfigContext.ts b/packages/config/src/processConfig/strategy/processConfigContext.ts new file mode 100644 index 0000000..d2b55b4 --- /dev/null +++ b/packages/config/src/processConfig/strategy/processConfigContext.ts @@ -0,0 +1,27 @@ +import { AzionConfig } from '../../types'; +import ProcessConfigStrategy from './processConfigStrategy'; + +/** + * ProcessConfigContext + * @class + * @description This class is responsible for generating the context of the process config. + */ +class ProcessConfigContext { + strategies: { [key: string]: ProcessConfigStrategy }; + constructor() { + this.strategies = {}; + } + + setStrategy(type: string, strategy: ProcessConfigStrategy): void { + this.strategies[type] = strategy; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + generate(config: AzionConfig, context: any) { + Object.keys(this.strategies).forEach((key) => { + context[key] = this.strategies[key].generate(config, context); + }); + return context; + } +} + +export default ProcessConfigContext; diff --git a/packages/config/src/processConfig/strategy/processConfigStrategy.ts b/packages/config/src/processConfig/strategy/processConfigStrategy.ts new file mode 100644 index 0000000..d1e8945 --- /dev/null +++ b/packages/config/src/processConfig/strategy/processConfigStrategy.ts @@ -0,0 +1,16 @@ +/** + * ProcessConfigStrategy + * @class ProcessConfigStrategy + * @description This class is the base class for all process config strategies. + */ + +import { AzionConfig } from '../../types'; + +class ProcessConfigStrategy { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + generate(config: AzionConfig, context: any) { + return context; + } +} + +export default ProcessConfigStrategy; diff --git a/packages/config/src/types.ts b/packages/config/src/types.ts index 93adbac..c29b49a 100644 --- a/packages/config/src/types.ts +++ b/packages/config/src/types.ts @@ -9,7 +9,9 @@ export type AzionDomain = { /** List of CNAMEs associated with the domain */ cnames?: string[]; /** Associated edge application ID */ - Id?: number; + id?: number; + /** Associated edge appliaction ID */ + edgeApplicationId?: number; /** Associated edge firewall ID */ edgeFirewallId?: number; /** Digital certificate ID */ @@ -262,10 +264,37 @@ export type AzionPurge = { layer?: 'edge_caching' | 'l2_caching'; }; +export type AzionBuild = { + /** Bunlder to be used */ + builder?: 'webpack' | 'esbuild'; + /** Entry file for the build */ + entry?: string; + /** Preset configuration e.g next */ + preset?: { + name: string; + }; + /** MemoryFS configuration */ + memoryFS?: { + /** List of directories to be injected */ + injectionDirs: string[]; + /** Remove path prefix */ + removePathPrefix: string; + }; + /** Polyfills enabled */ + polyfills?: boolean; + /** if true will use the owner worker with addEventListener */ + worker?: boolean; + /** Custom configuration to bundlers e.g minify: true */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + custom?: Record; +}; + /** * Main configuration type for Azion. */ export type AzionConfig = { + /** Build configuration */ + build?: AzionBuild; /** Domain configuration */ domain?: AzionDomain; /** Origin configurations */ diff --git a/packages/config/tsconfig.json b/packages/config/tsconfig.json index 69b56c1..9603f77 100644 --- a/packages/config/tsconfig.json +++ b/packages/config/tsconfig.json @@ -6,6 +6,9 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "moduleResolution": "node", + "isolatedModules": true, + "resolveJsonModule": true } } From edf4fced185bc541c377a5f1518846a81fbd2679 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 17 Sep 2024 14:42:24 +0000 Subject: [PATCH 45/45] chore(release): 1.7.0-stage.12 [skip ci] ## [1.7.0-stage.12](https://github.com/aziontech/lib/compare/v1.7.0-stage.11...v1.7.0-stage.12) (2024-09-17) ### Features * process config in azion/config (#41) ([9a6afc0](https://github.com/aziontech/lib/commit/9a6afc0233f356e0a20f60bcf39ed03347689fea)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c57388..4ac7fbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.7.0-stage.12](https://github.com/aziontech/lib/compare/v1.7.0-stage.11...v1.7.0-stage.12) (2024-09-17) + + +### Features + +* process config in azion/config (#41) ([9a6afc0](https://github.com/aziontech/lib/commit/9a6afc0233f356e0a20f60bcf39ed03347689fea)) + ## [1.7.0-stage.11](https://github.com/aziontech/lib/compare/v1.7.0-stage.10...v1.7.0-stage.11) (2024-09-10) diff --git a/package.json b/package.json index 15dcdcf..3767ad3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion", - "version": "1.7.0-stage.11", + "version": "1.7.0-stage.12", "description": "Azion Packages for Edge Computing.", "bin": { "azion": "./bin/azion"