From c4f5cb3d8fd39950520fe5ca221e3c48ffe9c393 Mon Sep 17 00:00:00 2001 From: Gonzalo Garcia Jaubert Date: Thu, 7 Apr 2022 11:33:32 +0100 Subject: [PATCH] fix: SortBy on Local and Azure Provider (#1044) * SortBy on Local and Azure Provider * Fix integration test --- docs/chapters/04_features.md | 41 + package-lock.json | 1316 ++++++++++------- .../src/booster-read-models-reader.ts | 1 + packages/framework-core/src/booster.ts | 3 + .../src/services/graphql/common.ts | 12 +- .../src/services/graphql/graphql-generator.ts | 13 +- .../graphql/graphql-query-generator.ts | 372 +---- .../graphql/graphql-subcriptions-generator.ts | 16 +- .../graphql-query-by-keys-generator.ts | 54 + .../graphql-query-events-generator.ts | 79 + .../graphql-query-filters-generator.ts | 33 + .../graphql-query-listed-generator.ts | 78 + .../graphql-query-filter-arguments-builder.ts | 140 ++ .../graphql-query-filter-fields-builder.ts | 32 + .../graphql-query-sort-builder.ts | 56 + .../test/booster-read-model-reader.test.ts | 1 + packages/framework-core/test/booster.test.ts | 1 + .../graphql/graphql-generator.test.ts | 2 + .../graphql/graphql-query-generator.test.ts | 351 ++--- .../graphql-query-by-keys-generator.test.ts | 132 ++ .../end-to-end/read-models.integration.ts | 247 +++- .../library/read-models-searcher-adapter.ts | 5 + .../src/helpers/query-helper.ts | 32 +- .../src/library/searcher-adapter.ts | 10 +- .../test/helpers/query-helper.test.ts | 26 + .../src/library/read-model-adapter.ts | 9 +- .../src/services/read-model-registry.ts | 34 +- .../test/helpers/read-model-helper.ts | 22 + .../test/library/read-model-adapter.test.ts | 168 ++- .../test/services/read-model-registry.test.ts | 33 +- packages/framework-types/src/envelope.ts | 6 +- packages/framework-types/src/provider.ts | 3 +- packages/framework-types/src/searcher.ts | 13 + .../framework-types/test/searcher.test.ts | 4 +- 34 files changed, 2154 insertions(+), 1191 deletions(-) create mode 100644 packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts create mode 100644 packages/framework-core/src/services/graphql/query-generators/graphql-query-events-generator.ts create mode 100644 packages/framework-core/src/services/graphql/query-generators/graphql-query-filters-generator.ts create mode 100644 packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts create mode 100644 packages/framework-core/src/services/graphql/query-helpers/graphql-query-filter-arguments-builder.ts create mode 100644 packages/framework-core/src/services/graphql/query-helpers/graphql-query-filter-fields-builder.ts create mode 100644 packages/framework-core/src/services/graphql/query-helpers/graphql-query-sort-builder.ts create mode 100644 packages/framework-core/test/services/graphql/query-generators/graphql-query-by-keys-generator.test.ts diff --git a/docs/chapters/04_features.md b/docs/chapters/04_features.md index 81a40cf83..1fa00ccae 100644 --- a/docs/chapters/04_features.md +++ b/docs/chapters/04_features.md @@ -961,6 +961,47 @@ export class GetProductsCount { > **Warning**: Notice that `ReadModel`s are eventually consistent objects that are calculated as all events in all entities that affect the read model are settled. You should not assume that a read model is a proper source of truth, so you shouldn't use this feature for data validations. If you need to query the most up-to-date current state, consider fetching your Entities, instead of ReadModels, with `Booster.entity` +#### Using sorting + +Booster allows you to sort your read models data in your commands handlers and event handlers using the `Booster.readModel` method. + +For example, you can sort and get the products in your commands like this: + +```graphql +{ + ListCartReadModels(filter: {}, limit: 5, sortBy: { + shippingAddress: { + firstName: ASC + } + }) { + items { + id + cartItems + checks + shippingAddress { + firstName + } + payment { + cartId + } + cartItemsIds + } + cursor + } +} +``` + +This is a preview feature available only for some Providers and with some limitations: +* Azure: + * Sort by one field supported. + * Nested fields supported. + * Sort by more than one file: **unsupported**. +* Local: + * Sort by one field supported. + * Nested fields supported. + * Sort by more than one file: **unsupported**. + +> **Warning**: It is not possible to sort by fields defined as Interface, only classes or primitives types. #### Using pagination diff --git a/package-lock.json b/package-lock.json index 1f01a356d..61cd987dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -326,9 +326,9 @@ } }, "node_modules/@aws-cdk/assets/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -372,9 +372,9 @@ } }, "node_modules/@aws-cdk/aws-apigateway/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -404,9 +404,9 @@ } }, "node_modules/@aws-cdk/aws-apigatewayv2/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -434,9 +434,9 @@ } }, "node_modules/@aws-cdk/aws-applicationautoscaling/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -490,9 +490,9 @@ } }, "node_modules/@aws-cdk/aws-autoscaling-common/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -528,17 +528,17 @@ } }, "node_modules/@aws-cdk/aws-autoscaling-hooktargets/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } }, "node_modules/@aws-cdk/aws-autoscaling/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -572,9 +572,9 @@ } }, "node_modules/@aws-cdk/aws-batch/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -602,9 +602,9 @@ } }, "node_modules/@aws-cdk/aws-certificatemanager/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -636,9 +636,9 @@ } }, "node_modules/@aws-cdk/aws-cloudformation/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -676,9 +676,9 @@ } }, "node_modules/@aws-cdk/aws-cloudfront/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -702,9 +702,9 @@ } }, "node_modules/@aws-cdk/aws-cloudwatch/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -757,9 +757,9 @@ } }, "node_modules/@aws-cdk/aws-codebuild/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -793,9 +793,9 @@ } }, "node_modules/@aws-cdk/aws-codecommit/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -819,9 +819,9 @@ } }, "node_modules/@aws-cdk/aws-codeguruprofiler/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -851,9 +851,9 @@ } }, "node_modules/@aws-cdk/aws-codepipeline/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -887,9 +887,9 @@ } }, "node_modules/@aws-cdk/aws-cognito/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -931,9 +931,9 @@ } }, "node_modules/@aws-cdk/aws-dynamodb/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -975,9 +975,9 @@ } }, "node_modules/@aws-cdk/aws-ec2/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1052,9 +1052,9 @@ "license": "MIT" }, "node_modules/@aws-cdk/aws-ecr-assets/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1071,9 +1071,9 @@ } }, "node_modules/@aws-cdk/aws-ecr/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1143,9 +1143,9 @@ } }, "node_modules/@aws-cdk/aws-ecs/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1175,9 +1175,9 @@ } }, "node_modules/@aws-cdk/aws-efs/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1201,9 +1201,9 @@ } }, "node_modules/@aws-cdk/aws-elasticloadbalancing/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1243,9 +1243,9 @@ } }, "node_modules/@aws-cdk/aws-elasticloadbalancingv2/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1317,17 +1317,17 @@ } }, "node_modules/@aws-cdk/aws-events-targets/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } }, "node_modules/@aws-cdk/aws-events/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1353,9 +1353,9 @@ } }, "node_modules/@aws-cdk/aws-globalaccelerator/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1379,9 +1379,9 @@ } }, "node_modules/@aws-cdk/aws-iam/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1409,9 +1409,9 @@ } }, "node_modules/@aws-cdk/aws-kinesis/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1433,9 +1433,9 @@ } }, "node_modules/@aws-cdk/aws-kinesisfirehose/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1461,9 +1461,9 @@ } }, "node_modules/@aws-cdk/aws-kms/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1561,17 +1561,17 @@ } }, "node_modules/@aws-cdk/aws-lambda-event-sources/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } }, "node_modules/@aws-cdk/aws-lambda/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1601,9 +1601,9 @@ } }, "node_modules/@aws-cdk/aws-logs/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1625,9 +1625,9 @@ } }, "node_modules/@aws-cdk/aws-msk/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1699,17 +1699,17 @@ } }, "node_modules/@aws-cdk/aws-route53-targets/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } }, "node_modules/@aws-cdk/aws-route53/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1765,9 +1765,9 @@ } }, "node_modules/@aws-cdk/aws-s3-assets/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1803,9 +1803,9 @@ } }, "node_modules/@aws-cdk/aws-s3-deployment/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1837,17 +1837,17 @@ } }, "node_modules/@aws-cdk/aws-s3-notifications/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } }, "node_modules/@aws-cdk/aws-s3/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1869,9 +1869,9 @@ } }, "node_modules/@aws-cdk/aws-sam/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1905,9 +1905,9 @@ } }, "node_modules/@aws-cdk/aws-secretsmanager/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1935,9 +1935,9 @@ } }, "node_modules/@aws-cdk/aws-servicediscovery/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -1959,9 +1959,9 @@ } }, "node_modules/@aws-cdk/aws-signer/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -2017,17 +2017,17 @@ } }, "node_modules/@aws-cdk/aws-sns-subscriptions/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } }, "node_modules/@aws-cdk/aws-sns/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -2055,9 +2055,9 @@ } }, "node_modules/@aws-cdk/aws-sqs/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -2085,9 +2085,9 @@ } }, "node_modules/@aws-cdk/aws-ssm/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -2119,9 +2119,9 @@ } }, "node_modules/@aws-cdk/aws-stepfunctions/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -2243,9 +2243,9 @@ "license": "MIT" }, "node_modules/@aws-cdk/core/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -2336,9 +2336,9 @@ } }, "node_modules/@aws-cdk/custom-resources/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -2410,9 +2410,9 @@ } }, "node_modules/@aws-cdk/lambda-layer-awscli/node_modules/constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==", + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==", "engines": { "node": ">= 12.7.0" } @@ -2440,6 +2440,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/@azure/arm-appservice/-/arm-appservice-6.1.0.tgz", "integrity": "sha512-CST99Ht+ziZ42zlCpIqKZ2vIrDHBStk9ODIEue08LU+AFIbRuXqR7DFyLv9Q8NldCvzKAXFfCA6LJiJgCYoEWw==", + "deprecated": "Please note, versions of this package with version numbers 9.0.0 and below have been deprecated as of 31-March-2022. We strongly encourage you to upgrade to version 10.0.0 or above to continue receiving updates. Refer to our deprecation policy: https://azure.github.io/azure-sdk/policies_support.html for more details.", "dependencies": { "@azure/ms-rest-azure-js": "^2.0.1", "@azure/ms-rest-js": "^2.0.4", @@ -2825,12 +2826,12 @@ } }, "node_modules/@boostercloud/framework-common-helpers": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/@boostercloud/framework-common-helpers/-/framework-common-helpers-0.26.7.tgz", - "integrity": "sha512-/v62gPMywW/+r49n8juYCLs4PxPWTnebUEvN+AoytvVWqlKinKnKEhq5zxyxVj6SsL6EMz9TMOlIg6NmDfxFaw==", + "version": "0.26.10", + "resolved": "https://registry.npmjs.org/@boostercloud/framework-common-helpers/-/framework-common-helpers-0.26.10.tgz", + "integrity": "sha512-JJ1SpAUQoCEsdiLuOJatef5IV6G252IpuWZjRqz1yQnIq4C5dJsoL1oaQuLGOEcW4qsp4t0emQu1ptWBD4rB2w==", "dev": true, "dependencies": { - "@boostercloud/framework-types": "^0.26.7", + "@boostercloud/framework-types": "^0.26.10", "child-process-promise": "^2.2.1", "tslib": "2.3.0" } @@ -2840,9 +2841,9 @@ "link": true }, "node_modules/@boostercloud/framework-types": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/@boostercloud/framework-types/-/framework-types-0.26.7.tgz", - "integrity": "sha512-tjIfm8Atl1oFm50BGsmGM2XOnRjwRuIriJVHYLl4WnbRExvK9bA6ajwwSV1i4hY2Lv7UNoLNdjH6OvF5eJhVmA==", + "version": "0.26.10", + "resolved": "https://registry.npmjs.org/@boostercloud/framework-types/-/framework-types-0.26.10.tgz", + "integrity": "sha512-odh6DZQwH6Okre0Yr6BIhpLtQJfDwcY4GDKBbEVmJ7VvdEpQmQi5dVsou8YqmUEHOnkNwOVaVkC9XMyGL3/XDw==", "dev": true, "dependencies": { "@types/graphql": "14.5.0", @@ -2890,9 +2891,9 @@ } }, "node_modules/@cdktf/hcl2cdk/node_modules/prettier": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.1.tgz", - "integrity": "sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", + "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", "bin": { "prettier": "bin-prettier.js" }, @@ -6007,9 +6008,9 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/@oclif/core": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@oclif/core/-/core-1.6.3.tgz", - "integrity": "sha512-a3DrPNlOYemwnzxuJ3tINjqpMVIYe56Mg+XaQo0nGsqGSk69wF5Q/hD8plsWrtwdkeIxwxhgl7T699EJypAUwg==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-1.6.4.tgz", + "integrity": "sha512-eUnh03MWxs3PHQUdZbo43ceLqmeOgGegsfimeqd6xfhcKwSv8dqarRyAoMCjg7P6Qm5qCjaNFZ6eS4ao349xvQ==", "dependencies": { "@oclif/linewrap": "^1.0.0", "@oclif/screen": "^3.0.2", @@ -6772,9 +6773,9 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "node_modules/@serverless/dashboard-plugin": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@serverless/dashboard-plugin/-/dashboard-plugin-6.2.0.tgz", - "integrity": "sha512-3vapxoW1hI8OBxLRJq+J9vt3tdckZObhYWfwRPDlqPs6FOEoHuJieAzz07e7YdWt7fO/LkQx6rlgCXlGamLnjw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@serverless/dashboard-plugin/-/dashboard-plugin-6.2.1.tgz", + "integrity": "sha512-ZvkDZsakoVluEbaQsskY1fW8puVjKjfjkMFAuXJnAPflGy0LCgT1ETGFEqCrAJwUJhgenPVxpZalO8J/22nzKw==", "dependencies": { "@serverless/event-mocks": "^1.1.1", "@serverless/platform-client": "^4.3.2", @@ -6784,7 +6785,7 @@ "flat": "^5.0.2", "fs-extra": "^9.1.0", "js-yaml": "^4.1.0", - "jszip": "^3.7.1", + "jszip": "^3.8.0", "lodash": "^4.17.21", "memoizee": "^0.4.15", "ncjsm": "^4.3.0", @@ -6792,7 +6793,7 @@ "node-fetch": "^2.6.7", "open": "^7.4.2", "semver": "^7.3.5", - "simple-git": "^3.4.0", + "simple-git": "^3.5.0", "type": "^2.6.0", "uuid": "^8.3.2", "yamljs": "^0.3.0" @@ -7450,6 +7451,11 @@ "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.7.tgz", "integrity": "sha512-S6+8JAYTE1qdsc9HMVsfY7+SgSuUU/Tp6TYTmITW0PZxiyIMvol3Gy//y69Wkhs0ti4py5qgR3uZH6uz/DNzJQ==" }, + "node_modules/@types/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==" + }, "node_modules/@types/json-schema": { "version": "7.0.9", "dev": true, @@ -10519,23 +10525,26 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/body-parser/node_modules/debug": { @@ -10546,11 +10555,33 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "license": "MIT", @@ -12325,12 +12356,12 @@ } }, "node_modules/cli-color": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.1.tgz", - "integrity": "sha512-eBbxZF6fqPUNnf7CLAFOersUnyYzv83tHFLSlts+OAHsNendaqv2tHCq+/MO+b3Y+9JeoUlIvobyxG/Z8GNeOg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.2.tgz", + "integrity": "sha512-g4JYjrTW9MGtCziFNjkqp3IMpGhnJyeB0lOtRPjQkYhXzKYr6tYnXKyEVnMzITxhpbahsEW9KsxOYIDKwcsIBw==", "dependencies": { "d": "^1.0.1", - "es5-ext": "^0.10.53", + "es5-ext": "^0.10.59", "es6-iterator": "^2.0.3", "memoizee": "^0.4.15", "timers-ext": "^0.1.7" @@ -12361,17 +12392,17 @@ } }, "node_modules/cli-progress-footer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cli-progress-footer/-/cli-progress-footer-2.3.0.tgz", - "integrity": "sha512-xJl+jqvdsE0Gjh5tKoLzZrQS4nPHC+yzeitgq2faAZiHl+/Peuwzoy5Sed6EBkm8JNrPk7W4U3YNVO/uxoqOFg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/cli-progress-footer/-/cli-progress-footer-2.3.1.tgz", + "integrity": "sha512-urD1hiEIQeZadVABtW5ExM8wse1phnmz15oJ4QEe46GQN87v1VBa0lZQ7gXkPELMzP6At4VY6v07baAiyztulw==", "dependencies": { - "cli-color": "^2.0.1", + "cli-color": "^2.0.2", "d": "^1.0.1", - "es5-ext": "^0.10.53", + "es5-ext": "^0.10.59", "mute-stream": "0.0.8", "process-utils": "^4.0.0", "timers-ext": "^0.1.7", - "type": "^2.5.0" + "type": "^2.6.0" }, "engines": { "node": ">=10.0" @@ -12799,6 +12830,18 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, + "node_modules/compress-brotli": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/compress-brotli/-/compress-brotli-1.3.6.tgz", + "integrity": "sha512-au99/GqZtUtiCBliqLFbWlhnCxn+XSYjwZ77q6mKN4La4qOXDoLVPZ50iXr0WmAyMxl8yqoq3Yq4OeQNPPkyeQ==", + "dependencies": { + "@types/json-buffer": "~3.0.0", + "json-buffer": "~3.0.1" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/compress-commons": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", @@ -12862,9 +12905,9 @@ "license": "ISC" }, "node_modules/constructs": { - "version": "10.0.101", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.0.101.tgz", - "integrity": "sha512-phhf91qVARDXp8jx9r63lSY2OqXUlg5KXjFm5UHeXI86YMmUWYUl2GsFOimZ6AW7Hvkre/Gue7D3TrPD69kP0g==", + "version": "10.0.108", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.0.108.tgz", + "integrity": "sha512-A5sZMVWEblwtPBAOScOt8lBn2epTUljGy/sVfHi7juLnSNSZd2qaO7t62qvbIWJEpH047uYGd755PomB+sXeHA==", "engines": { "node": ">= 12.7.0" } @@ -13164,13 +13207,9 @@ } }, "node_modules/crc-32": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.1.tgz", - "integrity": "sha512-Dn/xm/1vFFgs3nfrpEVScHoIslO9NZRITWGz/1E/St6u4xw99vfZzVkW0OSnzx2h9egej9xwMCEut6sqwokM/w==", - "dependencies": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.3.1" - }, + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "bin": { "crc32": "bin/crc32.njs" }, @@ -13831,9 +13870,13 @@ "license": "ISC" }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detect-indent": { "version": "5.0.0", @@ -14777,14 +14820,6 @@ "node": ">= 8" } }, - "node_modules/exit-on-epipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", - "engines": { - "node": ">=0.8" - } - }, "node_modules/expand-range": { "version": "1.8.2", "dev": true, @@ -14862,6 +14897,26 @@ "node": ">= 0.10.0" } }, + "node_modules/express/node_modules/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -14870,11 +14925,51 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/express/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/ext": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", @@ -15184,6 +15279,17 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-cache-dir": { "version": "3.3.2", "dev": true, @@ -16193,18 +16299,34 @@ } }, "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" } }, "node_modules/http-proxy-agent": { @@ -17958,9 +18080,9 @@ } }, "node_modules/jsii-rosetta/node_modules/@xmldom/xmldom": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.1.tgz", - "integrity": "sha512-4wOae+5N2RZ+CZXd9ZKwkaDi55IxrSTOjHpxTvQQ4fomtOJmqVxbmICA9jE1jvnqNhpfgz8cnfFagG86wV/xLQ==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.2.tgz", + "integrity": "sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ==", "engines": { "node": ">=10.0.0" } @@ -18028,9 +18150,9 @@ } }, "node_modules/jsii-srcmak": { - "version": "0.1.516", - "resolved": "https://registry.npmjs.org/jsii-srcmak/-/jsii-srcmak-0.1.516.tgz", - "integrity": "sha512-S2GLRtZkTmf6KCFY/uLc1lE3GN5kZlWcvLB0FAdrk5yAvkNhQZdYYSEBi3OBwGtN9DNZeRU4By5jjpp5UZlypA==", + "version": "0.1.523", + "resolved": "https://registry.npmjs.org/jsii-srcmak/-/jsii-srcmak-0.1.523.tgz", + "integrity": "sha512-b7vkOZnBcDYl/32tPn0cSPetl4znNTXP7AHDDi9rhP7rgIQx8YWDaPHZsGSpA6RgvC7aGBgHVVYLsC6qPeLJ7w==", "dependencies": { "fs-extra": "^9.1.0", "jsii": "^1.55.1", @@ -18518,9 +18640,9 @@ } }, "node_modules/jszip": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.8.0.tgz", - "integrity": "sha512-cnpQrXvFSLdsR9KR5/x7zdf6c3m8IhZfZzSblFEHSqBaVwD2nvJ4CuCKLyvKvwBgZm08CgfSoiTBQLm5WW9hGw==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.9.1.tgz", + "integrity": "sha512-H9A60xPqJ1CuC4Ka6qxzXZeU8aNmgOeP5IFqwJbQQwtu2EUYxota3LdsiZWplF7Wgd9tkAd0mdu36nceSaPuYw==", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -18607,10 +18729,11 @@ "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" }, "node_modules/keyv": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.1.1.tgz", - "integrity": "sha512-tGv1yP6snQVDSM4X6yxrv2zzq/EvpW+oYiUz6aueW1u9CtS8RzUQYxxmFwgZlO2jSgCxQbchhxaqXXp2hnKGpQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.2.2.tgz", + "integrity": "sha512-uYS0vKTlBIjNCAUqrjlxmruxOEiZxZIHXyp32sdcGmP+ukFrmWUnE//RcPXJH3Vxrni1H2gsQbjHE0bH7MtMQQ==", "dependencies": { + "compress-brotli": "^1.3.6", "json-buffer": "3.0.1" } }, @@ -20533,9 +20656,9 @@ } }, "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "version": "2.29.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", + "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==", "engines": { "node": "*" } @@ -21622,9 +21745,9 @@ } }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -22480,17 +22603,6 @@ "node": ">=6.0.0" } }, - "node_modules/printj": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.1.tgz", - "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==", - "bin": { - "printj": "bin/printj.njs" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/priorityqueuejs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz", @@ -22922,12 +23034,12 @@ } }, "node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dependencies": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -24182,11 +24294,42 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/send/node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/send/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/send/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/serialize-javascript": { "version": "5.0.1", "dev": true, @@ -24532,9 +24675,9 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/serverless/node_modules/aws-sdk": { - "version": "2.1104.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1104.0.tgz", - "integrity": "sha512-MQbjCtU+1el6XkHcOgC+FYTEdC/dQvG6Qn5kO3EbPmVKctMRjuEXYzpRB+vTYR+iZbdJYBE7A6EEthdg3iQkbA==", + "version": "2.1109.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1109.0.tgz", + "integrity": "sha512-kcxDBPIpgN2mTgSxbbTPA5l63mCImDY+1YfZGAkZ9LIk+LYX3CPQC1vVP/iMrGioToB0KLSLechNuBif/6LXUA==", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -25101,9 +25244,9 @@ "license": "CC0-1.0" }, "node_modules/spdx-license-list": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/spdx-license-list/-/spdx-license-list-6.4.0.tgz", - "integrity": "sha512-4BxgJ1IZxTJuX1YxMGu2cRYK46Bk9zJNTK2/R0wNZR0cm+6SVl26/uG7FQmQtxoJQX1uZ0EpTi2L7zvMLboaBA==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/spdx-license-list/-/spdx-license-list-6.5.0.tgz", + "integrity": "sha512-28O9GgFrMg2Wp8tVML0Zk+fnXaECy7UbB6pxo+93whHay/nqPyEdM07Cx6B4+j2pniUZTYr57OaIOyNTxsTwtw==", "engines": { "node": ">=8" }, @@ -25150,9 +25293,9 @@ } }, "node_modules/sscaff": { - "version": "1.2.243", - "resolved": "https://registry.npmjs.org/sscaff/-/sscaff-1.2.243.tgz", - "integrity": "sha512-yoHpVVf9Qmpe43AWyzt/MufdWi1YJqU+UvFRQskycGvDt/I9e9z0OKK9WvGfdYC1THcqWIYRJO0do5gEjQQ/EA==", + "version": "1.2.250", + "resolved": "https://registry.npmjs.org/sscaff/-/sscaff-1.2.250.tgz", + "integrity": "sha512-J9asMSddhWmXhjcpm6THvrOgWliXPKmjr6Yzpto64cNPrQOI0RYd8pVTuQNg5bZPDM1/4XM4ywzFcBM7vjeilw==", "engines": { "node": ">= 12.13.0" } @@ -27319,12 +27462,12 @@ }, "packages/framework-provider-local": { "name": "@boostercloud/framework-provider-local", - "version": "0.26.7", + "version": "0.26.10", "dev": true, "license": "Apache-2.0", "dependencies": { - "@boostercloud/framework-common-helpers": "^0.26.7", - "@boostercloud/framework-types": "^0.26.7", + "@boostercloud/framework-common-helpers": "^0.26.10", + "@boostercloud/framework-types": "^0.26.10", "@types/nedb": "^1.8.11", "nedb": "^1.8.0", "tslib": "2.3.0" @@ -27489,9 +27632,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27516,9 +27659,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27536,9 +27679,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27555,9 +27698,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27578,9 +27721,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27595,9 +27738,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27618,9 +27761,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27640,9 +27783,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27659,9 +27802,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27680,9 +27823,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27704,9 +27847,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27721,9 +27864,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27751,9 +27894,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" }, "yaml": { "version": "1.10.2", @@ -27773,9 +27916,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27790,9 +27933,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27810,9 +27953,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27831,9 +27974,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" }, "punycode": { "version": "2.1.1", @@ -27857,9 +28000,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27883,9 +28026,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27901,9 +28044,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -27939,9 +28082,9 @@ "bundled": true }, "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" }, "minimatch": { "version": "3.0.4", @@ -27986,9 +28129,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28006,9 +28149,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28023,9 +28166,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28048,9 +28191,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28065,9 +28208,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28097,9 +28240,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28115,9 +28258,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28132,9 +28275,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28151,9 +28294,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28167,9 +28310,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28185,9 +28328,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28217,9 +28360,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28247,9 +28390,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28267,9 +28410,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28283,9 +28426,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28304,9 +28447,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28332,9 +28475,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28352,9 +28495,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28373,9 +28516,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28396,9 +28539,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28417,9 +28560,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28433,9 +28576,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28455,9 +28598,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28474,9 +28617,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28490,9 +28633,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28511,9 +28654,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28531,9 +28674,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28550,9 +28693,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28569,9 +28712,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28590,9 +28733,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28669,9 +28812,9 @@ "bundled": true }, "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" }, "fs-extra": { "version": "9.1.0", @@ -28728,9 +28871,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -28774,9 +28917,9 @@ }, "dependencies": { "constructs": { - "version": "3.3.254", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.254.tgz", - "integrity": "sha512-BHxn3xjvRzlVmIv5zKeRUqJP2f/OYcWB0NKV9WOvOzaql0OcJfmQltKTfIMHrfCBteM+/z4MOVkjskJGz1W8jw==" + "version": "3.3.261", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-3.3.261.tgz", + "integrity": "sha512-WAcluBHRkIlTIW0jXOhp9Fm8ZtjS2cF3MbAxN6/VEdAVtjuXlHqKmrBOVIp8V5L26ze/xuMz3mDhq1CrC2pjsw==" } } }, @@ -29082,12 +29225,12 @@ } }, "@boostercloud/framework-common-helpers": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/@boostercloud/framework-common-helpers/-/framework-common-helpers-0.26.7.tgz", - "integrity": "sha512-/v62gPMywW/+r49n8juYCLs4PxPWTnebUEvN+AoytvVWqlKinKnKEhq5zxyxVj6SsL6EMz9TMOlIg6NmDfxFaw==", + "version": "0.26.10", + "resolved": "https://registry.npmjs.org/@boostercloud/framework-common-helpers/-/framework-common-helpers-0.26.10.tgz", + "integrity": "sha512-JJ1SpAUQoCEsdiLuOJatef5IV6G252IpuWZjRqz1yQnIq4C5dJsoL1oaQuLGOEcW4qsp4t0emQu1ptWBD4rB2w==", "dev": true, "requires": { - "@boostercloud/framework-types": "^0.26.7", + "@boostercloud/framework-types": "^0.26.10", "child-process-promise": "^2.2.1", "tslib": "2.3.0" } @@ -29095,8 +29238,8 @@ "@boostercloud/framework-provider-local": { "version": "file:packages/framework-provider-local", "requires": { - "@boostercloud/framework-common-helpers": "^0.26.7", - "@boostercloud/framework-types": "^0.26.7", + "@boostercloud/framework-common-helpers": "^0.26.10", + "@boostercloud/framework-types": "^0.26.10", "@types/express": "4.17.12", "@types/faker": "5.1.5", "@types/nedb": "^1.8.11", @@ -29111,9 +29254,9 @@ } }, "@boostercloud/framework-types": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/@boostercloud/framework-types/-/framework-types-0.26.7.tgz", - "integrity": "sha512-tjIfm8Atl1oFm50BGsmGM2XOnRjwRuIriJVHYLl4WnbRExvK9bA6ajwwSV1i4hY2Lv7UNoLNdjH6OvF5eJhVmA==", + "version": "0.26.10", + "resolved": "https://registry.npmjs.org/@boostercloud/framework-types/-/framework-types-0.26.10.tgz", + "integrity": "sha512-odh6DZQwH6Okre0Yr6BIhpLtQJfDwcY4GDKBbEVmJ7VvdEpQmQi5dVsou8YqmUEHOnkNwOVaVkC9XMyGL3/XDw==", "dev": true, "requires": { "@types/graphql": "14.5.0", @@ -29155,9 +29298,9 @@ } }, "prettier": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.1.tgz", - "integrity": "sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", + "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==" } } }, @@ -31383,9 +31526,9 @@ } }, "@oclif/core": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@oclif/core/-/core-1.6.3.tgz", - "integrity": "sha512-a3DrPNlOYemwnzxuJ3tINjqpMVIYe56Mg+XaQo0nGsqGSk69wF5Q/hD8plsWrtwdkeIxwxhgl7T699EJypAUwg==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-1.6.4.tgz", + "integrity": "sha512-eUnh03MWxs3PHQUdZbo43ceLqmeOgGegsfimeqd6xfhcKwSv8dqarRyAoMCjg7P6Qm5qCjaNFZ6eS4ao349xvQ==", "requires": { "@oclif/linewrap": "^1.0.0", "@oclif/screen": "^3.0.2", @@ -31990,9 +32133,9 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@serverless/dashboard-plugin": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@serverless/dashboard-plugin/-/dashboard-plugin-6.2.0.tgz", - "integrity": "sha512-3vapxoW1hI8OBxLRJq+J9vt3tdckZObhYWfwRPDlqPs6FOEoHuJieAzz07e7YdWt7fO/LkQx6rlgCXlGamLnjw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@serverless/dashboard-plugin/-/dashboard-plugin-6.2.1.tgz", + "integrity": "sha512-ZvkDZsakoVluEbaQsskY1fW8puVjKjfjkMFAuXJnAPflGy0LCgT1ETGFEqCrAJwUJhgenPVxpZalO8J/22nzKw==", "requires": { "@serverless/event-mocks": "^1.1.1", "@serverless/platform-client": "^4.3.2", @@ -32002,7 +32145,7 @@ "flat": "^5.0.2", "fs-extra": "^9.1.0", "js-yaml": "^4.1.0", - "jszip": "^3.7.1", + "jszip": "^3.8.0", "lodash": "^4.17.21", "memoizee": "^0.4.15", "ncjsm": "^4.3.0", @@ -32010,7 +32153,7 @@ "node-fetch": "^2.6.7", "open": "^7.4.2", "semver": "^7.3.5", - "simple-git": "^3.4.0", + "simple-git": "^3.5.0", "type": "^2.6.0", "uuid": "^8.3.2", "yamljs": "^0.3.0" @@ -32553,6 +32696,11 @@ "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.7.tgz", "integrity": "sha512-S6+8JAYTE1qdsc9HMVsfY7+SgSuUU/Tp6TYTmITW0PZxiyIMvol3Gy//y69Wkhs0ti4py5qgR3uZH6uz/DNzJQ==" }, + "@types/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==" + }, "@types/json-schema": { "version": "7.0.9", "dev": true @@ -35273,20 +35421,22 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "debug": { @@ -35297,10 +35447,23 @@ "ms": "2.0.0" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } } } }, @@ -36527,12 +36690,12 @@ "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" }, "cli-color": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.1.tgz", - "integrity": "sha512-eBbxZF6fqPUNnf7CLAFOersUnyYzv83tHFLSlts+OAHsNendaqv2tHCq+/MO+b3Y+9JeoUlIvobyxG/Z8GNeOg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.2.tgz", + "integrity": "sha512-g4JYjrTW9MGtCziFNjkqp3IMpGhnJyeB0lOtRPjQkYhXzKYr6tYnXKyEVnMzITxhpbahsEW9KsxOYIDKwcsIBw==", "requires": { "d": "^1.0.1", - "es5-ext": "^0.10.53", + "es5-ext": "^0.10.59", "es6-iterator": "^2.0.3", "memoizee": "^0.4.15", "timers-ext": "^0.1.7" @@ -36553,17 +36716,17 @@ } }, "cli-progress-footer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cli-progress-footer/-/cli-progress-footer-2.3.0.tgz", - "integrity": "sha512-xJl+jqvdsE0Gjh5tKoLzZrQS4nPHC+yzeitgq2faAZiHl+/Peuwzoy5Sed6EBkm8JNrPk7W4U3YNVO/uxoqOFg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/cli-progress-footer/-/cli-progress-footer-2.3.1.tgz", + "integrity": "sha512-urD1hiEIQeZadVABtW5ExM8wse1phnmz15oJ4QEe46GQN87v1VBa0lZQ7gXkPELMzP6At4VY6v07baAiyztulw==", "requires": { - "cli-color": "^2.0.1", + "cli-color": "^2.0.2", "d": "^1.0.1", - "es5-ext": "^0.10.53", + "es5-ext": "^0.10.59", "mute-stream": "0.0.8", "process-utils": "^4.0.0", "timers-ext": "^0.1.7", - "type": "^2.5.0" + "type": "^2.6.0" } }, "cli-spinners": { @@ -36875,6 +37038,15 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, + "compress-brotli": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/compress-brotli/-/compress-brotli-1.3.6.tgz", + "integrity": "sha512-au99/GqZtUtiCBliqLFbWlhnCxn+XSYjwZ77q6mKN4La4qOXDoLVPZ50iXr0WmAyMxl8yqoq3Yq4OeQNPPkyeQ==", + "requires": { + "@types/json-buffer": "~3.0.0", + "json-buffer": "~3.0.1" + } + }, "compress-commons": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", @@ -36925,9 +37097,9 @@ "dev": true }, "constructs": { - "version": "10.0.101", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.0.101.tgz", - "integrity": "sha512-phhf91qVARDXp8jx9r63lSY2OqXUlg5KXjFm5UHeXI86YMmUWYUl2GsFOimZ6AW7Hvkre/Gue7D3TrPD69kP0g==" + "version": "10.0.108", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.0.108.tgz", + "integrity": "sha512-A5sZMVWEblwtPBAOScOt8lBn2epTUljGy/sVfHi7juLnSNSZd2qaO7t62qvbIWJEpH047uYGd755PomB+sXeHA==" }, "contains-path": { "version": "0.1.0", @@ -37153,13 +37325,9 @@ } }, "crc-32": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.1.tgz", - "integrity": "sha512-Dn/xm/1vFFgs3nfrpEVScHoIslO9NZRITWGz/1E/St6u4xw99vfZzVkW0OSnzx2h9egej9xwMCEut6sqwokM/w==", - "requires": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.3.1" - } + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==" }, "crc32-stream": { "version": "4.0.2", @@ -37639,9 +37807,9 @@ "dev": true }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "detect-indent": { "version": "5.0.0" @@ -38323,11 +38491,6 @@ } } }, - "exit-on-epipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" - }, "expand-range": { "version": "1.8.2", "dev": true, @@ -38392,6 +38555,23 @@ "vary": "~1.1.2" }, "dependencies": { + "body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -38400,10 +38580,41 @@ "ms": "2.0.0" } }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "requires": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } } } }, @@ -38625,6 +38836,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } } } }, @@ -39285,15 +39504,27 @@ } }, "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + } } }, "http-proxy-agent": { @@ -40527,9 +40758,9 @@ }, "dependencies": { "@xmldom/xmldom": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.1.tgz", - "integrity": "sha512-4wOae+5N2RZ+CZXd9ZKwkaDi55IxrSTOjHpxTvQQ4fomtOJmqVxbmICA9jE1jvnqNhpfgz8cnfFagG86wV/xLQ==" + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.2.tgz", + "integrity": "sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ==" }, "fs-extra": { "version": "9.1.0", @@ -40578,9 +40809,9 @@ } }, "jsii-srcmak": { - "version": "0.1.516", - "resolved": "https://registry.npmjs.org/jsii-srcmak/-/jsii-srcmak-0.1.516.tgz", - "integrity": "sha512-S2GLRtZkTmf6KCFY/uLc1lE3GN5kZlWcvLB0FAdrk5yAvkNhQZdYYSEBi3OBwGtN9DNZeRU4By5jjpp5UZlypA==", + "version": "0.1.523", + "resolved": "https://registry.npmjs.org/jsii-srcmak/-/jsii-srcmak-0.1.523.tgz", + "integrity": "sha512-b7vkOZnBcDYl/32tPn0cSPetl4znNTXP7AHDDi9rhP7rgIQx8YWDaPHZsGSpA6RgvC7aGBgHVVYLsC6qPeLJ7w==", "requires": { "fs-extra": "^9.1.0", "jsii": "^1.55.1", @@ -40848,9 +41079,9 @@ } }, "jszip": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.8.0.tgz", - "integrity": "sha512-cnpQrXvFSLdsR9KR5/x7zdf6c3m8IhZfZzSblFEHSqBaVwD2nvJ4CuCKLyvKvwBgZm08CgfSoiTBQLm5WW9hGw==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.9.1.tgz", + "integrity": "sha512-H9A60xPqJ1CuC4Ka6qxzXZeU8aNmgOeP5IFqwJbQQwtu2EUYxota3LdsiZWplF7Wgd9tkAd0mdu36nceSaPuYw==", "requires": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -40935,10 +41166,11 @@ "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" }, "keyv": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.1.1.tgz", - "integrity": "sha512-tGv1yP6snQVDSM4X6yxrv2zzq/EvpW+oYiUz6aueW1u9CtS8RzUQYxxmFwgZlO2jSgCxQbchhxaqXXp2hnKGpQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.2.2.tgz", + "integrity": "sha512-uYS0vKTlBIjNCAUqrjlxmruxOEiZxZIHXyp32sdcGmP+ukFrmWUnE//RcPXJH3Vxrni1H2gsQbjHE0bH7MtMQQ==", "requires": { + "compress-brotli": "^1.3.6", "json-buffer": "3.0.1" } }, @@ -42288,9 +42520,9 @@ "dev": true }, "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + "version": "2.29.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", + "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==" }, "ms": { "version": "2.1.2" @@ -43091,9 +43323,9 @@ "integrity": "sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==" }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -43672,11 +43904,6 @@ "fast-diff": "^1.1.2" } }, - "printj": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.1.tgz", - "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==" - }, "priorityqueuejs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz", @@ -43979,12 +44206,12 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -44869,10 +45096,35 @@ } } }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } } } }, @@ -44979,9 +45231,9 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "aws-sdk": { - "version": "2.1104.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1104.0.tgz", - "integrity": "sha512-MQbjCtU+1el6XkHcOgC+FYTEdC/dQvG6Qn5kO3EbPmVKctMRjuEXYzpRB+vTYR+iZbdJYBE7A6EEthdg3iQkbA==", + "version": "2.1109.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1109.0.tgz", + "integrity": "sha512-kcxDBPIpgN2mTgSxbbTPA5l63mCImDY+1YfZGAkZ9LIk+LYX3CPQC1vVP/iMrGioToB0KLSLechNuBif/6LXUA==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -45559,9 +45811,9 @@ "version": "3.0.11" }, "spdx-license-list": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/spdx-license-list/-/spdx-license-list-6.4.0.tgz", - "integrity": "sha512-4BxgJ1IZxTJuX1YxMGu2cRYK46Bk9zJNTK2/R0wNZR0cm+6SVl26/uG7FQmQtxoJQX1uZ0EpTi2L7zvMLboaBA==" + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/spdx-license-list/-/spdx-license-list-6.5.0.tgz", + "integrity": "sha512-28O9GgFrMg2Wp8tVML0Zk+fnXaECy7UbB6pxo+93whHay/nqPyEdM07Cx6B4+j2pniUZTYr57OaIOyNTxsTwtw==" }, "split": { "version": "1.0.1", @@ -45592,9 +45844,9 @@ } }, "sscaff": { - "version": "1.2.243", - "resolved": "https://registry.npmjs.org/sscaff/-/sscaff-1.2.243.tgz", - "integrity": "sha512-yoHpVVf9Qmpe43AWyzt/MufdWi1YJqU+UvFRQskycGvDt/I9e9z0OKK9WvGfdYC1THcqWIYRJO0do5gEjQQ/EA==" + "version": "1.2.250", + "resolved": "https://registry.npmjs.org/sscaff/-/sscaff-1.2.250.tgz", + "integrity": "sha512-J9asMSddhWmXhjcpm6THvrOgWliXPKmjr6Yzpto64cNPrQOI0RYd8pVTuQNg5bZPDM1/4XM4ywzFcBM7vjeilw==" }, "sshpk": { "version": "1.17.0", diff --git a/packages/framework-core/src/booster-read-models-reader.ts b/packages/framework-core/src/booster-read-models-reader.ts index 6380de73c..8e1159bf3 100644 --- a/packages/framework-core/src/booster-read-models-reader.ts +++ b/packages/framework-core/src/booster-read-models-reader.ts @@ -52,6 +52,7 @@ export class BoosterReadModelsReader { return Booster.readModel(readModelMetadata.class) .filter(readModelTransformedRequest.filters) + .sortBy(readModelTransformedRequest.sortBy) .limit(readModelTransformedRequest.limit) .afterCursor(readModelTransformedRequest.afterCursor) .paginatedVersion(readModelTransformedRequest.paginatedVersion) diff --git a/packages/framework-core/src/booster.ts b/packages/framework-core/src/booster.ts index 8c9e8ba0a..435856262 100644 --- a/packages/framework-core/src/booster.ts +++ b/packages/framework-core/src/booster.ts @@ -13,6 +13,7 @@ import { Searcher, SearcherFunction, SequenceKey, + SortFor, UUID, } from '@boostercloud/framework-types' import { BoosterEventDispatcher } from './booster-event-dispatcher' @@ -82,6 +83,7 @@ export class Booster { const searchFunction: SearcherFunction = async ( readModelName: string, filters: FilterFor, + sort?: SortFor, limit?: number, afterCursor?: any, paginatedVersion?: boolean @@ -91,6 +93,7 @@ export class Booster { this.logger, readModelName, filters, + sort, limit, afterCursor, paginatedVersion diff --git a/packages/framework-core/src/services/graphql/common.ts b/packages/framework-core/src/services/graphql/common.ts index 21a78aa46..0526332b7 100644 --- a/packages/framework-core/src/services/graphql/common.ts +++ b/packages/framework-core/src/services/graphql/common.ts @@ -7,7 +7,7 @@ import { ReadModelInterface, ContextEnvelope, } from '@boostercloud/framework-types' -import { GraphQLFieldResolver } from 'graphql' +import { GraphQLEnumType, GraphQLEnumValueConfigMap, GraphQLFieldResolver } from 'graphql' import { ReadModelPubSub } from '../pub-sub/read-model-pub-sub' import { PropertyMetadata } from 'metadata-booster' @@ -35,3 +35,13 @@ export interface GraphQLResolverContext { export const graphQLWebsocketSubprotocolHeaders = { 'Sec-WebSocket-Protocol': 'graphql-ws', } + +export const buildGraphqlSimpleEnumFor = (enumName: string, values: Array): GraphQLEnumType => { + return new GraphQLEnumType({ + name: enumName, + values: values.reduce((valuesRecord, value) => { + valuesRecord[value] = { value } + return valuesRecord + }, {} as GraphQLEnumValueConfigMap), + }) +} diff --git a/packages/framework-core/src/services/graphql/graphql-generator.ts b/packages/framework-core/src/services/graphql/graphql-generator.ts index 683ed789a..886290edc 100644 --- a/packages/framework-core/src/services/graphql/graphql-generator.ts +++ b/packages/framework-core/src/services/graphql/graphql-generator.ts @@ -14,7 +14,7 @@ import { ReadModelRequestProperties, TimeKey, } from '@boostercloud/framework-types' -import { GraphQLFieldResolver, GraphQLResolveInfo, GraphQLSchema } from 'graphql' +import { GraphQLFieldResolver, GraphQLInputObjectType, GraphQLResolveInfo, GraphQLSchema } from 'graphql' import { pluralize } from 'inflected' import { BoosterCommandDispatcher } from '../../booster-command-dispatcher' import { BoosterEventsReader } from '../../booster-events-reader' @@ -42,13 +42,16 @@ export class GraphQLGenerator { ...config.commandHandlers, }) + const generatedFiltersByTypeName: Record = {} + const queryGenerator = new GraphQLQueryGenerator( config, config.readModels, typeInformer, this.readModelByIDResolverBuilder.bind(this, config), this.readModelResolverBuilder.bind(this), - this.eventResolver.bind(this) + this.eventResolver.bind(this), + generatedFiltersByTypeName ) const mutationGenerator = new GraphQLMutationGenerator( @@ -60,9 +63,9 @@ export class GraphQLGenerator { const subscriptionGenerator = new GraphQLSubscriptionGenerator( config.readModels, typeInformer, - queryGenerator, this.subscriptionByIDResolverBuilder.bind(this, config), - this.subscriptionResolverBuilder.bind(this, config) + this.subscriptionResolverBuilder.bind(this, config), + generatedFiltersByTypeName ) this.schema = new GraphQLSchema({ @@ -176,6 +179,7 @@ export class GraphQLGenerator { key, version: 1, // TODO: How to pass the version through GraphQL? filters: {}, + sortBy: {}, } } } @@ -192,6 +196,7 @@ function toReadModelRequestEnvelope( class: readModelClass, className: readModelClass.name, filters: args.filter ?? {}, + sortBy: args.sortBy ?? {}, limit: args.limit, afterCursor: args.afterCursor, paginatedVersion, diff --git a/packages/framework-core/src/services/graphql/graphql-query-generator.ts b/packages/framework-core/src/services/graphql/graphql-query-generator.ts index c193c3ae7..885380943 100644 --- a/packages/framework-core/src/services/graphql/graphql-query-generator.ts +++ b/packages/framework-core/src/services/graphql/graphql-query-generator.ts @@ -1,342 +1,56 @@ -import { AnyClass, BoosterConfig } from '@boostercloud/framework-types' -import { - GraphQLBoolean, - GraphQLEnumType, - GraphQLEnumValueConfigMap, - GraphQLFieldConfig, - GraphQLFieldConfigArgumentMap, - GraphQLFieldConfigMap, - GraphQLFieldResolver, - GraphQLFloat, - GraphQLID, - GraphQLInputFieldConfigMap, - GraphQLInputObjectType, - GraphQLInt, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLOutputType, - GraphQLScalarType, - GraphQLString, - Thunk, -} from 'graphql' -import { GraphQLJSONObject } from 'graphql-type-json' -import * as inflected from 'inflected' -import { PropertyMetadata } from 'metadata-booster' -import { getPropertiesMetadata } from '../../decorators/metadata' -import { GraphQLResolverContext, ResolverBuilder, TargetTypeMetadata, TargetTypesMap } from './common' +import { BoosterConfig } from '@boostercloud/framework-types' +import { GraphQLFieldResolver, GraphQLInputObjectType, GraphQLObjectType } from 'graphql' +import { GraphQLResolverContext, ResolverBuilder, TargetTypesMap } from './common' import { GraphQLTypeInformer } from './graphql-type-informer' +import { GraphqlQueryByKeysGenerator } from './query-generators/graphql-query-by-keys-generator' +import { GraphqlQueryEventsGenerator } from './query-generators/graphql-query-events-generator' +import { GraphqlQueryFiltersGenerator } from './query-generators/graphql-query-filters-generator' +import { GraphqlQueryListedGenerator } from './query-generators/graphql-query-listed-generator' export class GraphQLQueryGenerator { - private generatedFiltersByTypeName: Record = {} + private graphqlQueryByKeysGenerator: GraphqlQueryByKeysGenerator + private graphqlQueryFiltersGenerator: GraphqlQueryFiltersGenerator + private graphqlQueryListedGenerator: GraphqlQueryListedGenerator + private graphqlQueryEventsGenerator: GraphqlQueryEventsGenerator public constructor( - private readonly config: BoosterConfig, - private readonly readModelsMetadata: TargetTypesMap, - private readonly typeInformer: GraphQLTypeInformer, - private readonly byIDResolverBuilder: ResolverBuilder, - private readonly filterResolverBuilder: ResolverBuilder, - private readonly eventsResolver: GraphQLFieldResolver - ) {} + protected readonly config: BoosterConfig, + protected readonly readModelsMetadata: TargetTypesMap, + protected readonly typeInformer: GraphQLTypeInformer, + protected readonly byIDResolverBuilder: ResolverBuilder, + protected readonly filterResolverBuilder: ResolverBuilder, + protected readonly eventsResolver: GraphQLFieldResolver, + protected generatedFiltersByTypeName: Record = {} + ) { + this.graphqlQueryByKeysGenerator = new GraphqlQueryByKeysGenerator( + config, + readModelsMetadata, + typeInformer, + byIDResolverBuilder + ) + this.graphqlQueryFiltersGenerator = new GraphqlQueryFiltersGenerator( + readModelsMetadata, + typeInformer, + filterResolverBuilder, + generatedFiltersByTypeName + ) + this.graphqlQueryListedGenerator = new GraphqlQueryListedGenerator( + readModelsMetadata, + typeInformer, + filterResolverBuilder, + generatedFiltersByTypeName + ) + this.graphqlQueryEventsGenerator = new GraphqlQueryEventsGenerator(config, byIDResolverBuilder, eventsResolver) + } public generate(): GraphQLObjectType { - const byIDQueries = this.generateByKeysQueries() - const filterQueries = this.generateFilterQueries() - const listedQueries = this.generateListedQueries() - const eventQueries = this.generateEventQueries() + const byIDQueries = this.graphqlQueryByKeysGenerator.generateByKeysQueries() + const filterQueries = this.graphqlQueryFiltersGenerator.generateFilterQueries() + const listedQueries = this.graphqlQueryListedGenerator.generateListedQueries() + const eventQueries = this.graphqlQueryEventsGenerator.generateEventQueries() return new GraphQLObjectType({ name: 'Query', fields: { ...byIDQueries, ...filterQueries, ...listedQueries, ...eventQueries }, }) } - - private generateByKeysQueries(): GraphQLFieldConfigMap { - const queries: GraphQLFieldConfigMap = {} - for (const readModelName in this.readModelsMetadata) { - const sequenceKeyName = this.config.readModelSequenceKeys[readModelName] - if (sequenceKeyName) { - queries[readModelName] = this.generateByIdAndSequenceKeyQuery(readModelName, sequenceKeyName) - } else { - queries[readModelName] = this.generateByIdQuery(readModelName) - } - } - return queries - } - - private generateByIdQuery(readModelName: string): GraphQLFieldConfig { - const readModelMetadata = this.readModelsMetadata[readModelName] - const graphQLType = this.typeInformer.getGraphQLTypeFor(readModelMetadata.class) - return { - type: graphQLType, - args: { - id: { type: new GraphQLNonNull(GraphQLID) }, - }, - resolve: this.byIDResolverBuilder(readModelMetadata.class), - } - } - - private generateByIdAndSequenceKeyQuery( - readModelName: string, - sequenceKeyName: string - ): GraphQLFieldConfig { - const readModelMetadata = this.readModelsMetadata[readModelName] - const graphQLType = this.typeInformer.getGraphQLTypeFor(readModelMetadata.class) - return { - type: new GraphQLList(graphQLType), - args: { - id: { type: new GraphQLNonNull(GraphQLID) }, - [sequenceKeyName]: { type: GraphQLID }, - }, - resolve: this.byIDResolverBuilder(readModelMetadata.class), - } - } - - private generateFilterQueries(): GraphQLFieldConfigMap { - const queries: GraphQLFieldConfigMap = {} - for (const name in this.readModelsMetadata) { - const type = this.readModelsMetadata[name] - const graphQLType = this.typeInformer.getGraphQLTypeFor(type.class) - queries[inflected.pluralize(name)] = { - type: new GraphQLList(graphQLType), - args: this.generateFilterQueriesFields(name, type), - resolve: this.filterResolverBuilder(type.class), - } - } - return queries - } - - private generateListedQueries(): GraphQLFieldConfigMap { - const queries: GraphQLFieldConfigMap = {} - for (const name in this.readModelsMetadata) { - const type = this.readModelsMetadata[name] - const graphQLType = this.typeInformer.getGraphQLTypeFor(type.class) - queries[`List${inflected.pluralize(name)}`] = { - type: new GraphQLObjectType({ - name: `${name}Connection`, - fields: { - items: { type: new GraphQLList(graphQLType) }, - cursor: { type: GraphQLJSONObject }, - }, - }), - args: this.generateListedQueriesFields(name, type), - resolve: this.filterResolverBuilder(type.class), - } - } - return queries - } - - private generateEventQueries(): GraphQLFieldConfigMap { - const eventQueryResponse = this.buildEventQueryResponse() - return { - eventsByEntity: { - type: eventQueryResponse, - args: { - entity: { - type: new GraphQLNonNull(this.buildGraphqlSimpleEnumFor('EntityType', Object.keys(this.config.entities))), - }, - entityID: { type: GraphQLID }, - from: { type: GraphQLString }, - to: { type: GraphQLString }, - limit: { type: GraphQLInt }, - }, - resolve: this.eventsResolver, - }, - eventsByType: { - type: eventQueryResponse, - args: { - type: { - type: new GraphQLNonNull(this.buildGraphqlSimpleEnumFor('EventType', Object.keys(this.config.reducers))), - }, - from: { type: GraphQLString }, - to: { type: GraphQLString }, - limit: { type: GraphQLInt }, - }, - resolve: this.eventsResolver, - }, - } - } - - private buildEventQueryResponse(): GraphQLOutputType { - return new GraphQLList( - new GraphQLObjectType({ - name: 'EventQueryResponse', - fields: { - type: { type: new GraphQLNonNull(GraphQLString) }, - entity: { type: new GraphQLNonNull(GraphQLString) }, - entityID: { type: new GraphQLNonNull(GraphQLID) }, - requestID: { type: new GraphQLNonNull(GraphQLID) }, - user: { - type: new GraphQLObjectType({ - name: 'User', - fields: { - id: { type: GraphQLString }, - username: { type: new GraphQLNonNull(GraphQLString) }, - roles: { type: new GraphQLList(new GraphQLNonNull(GraphQLString)) }, - }, - }), - }, - createdAt: { type: new GraphQLNonNull(GraphQLString) }, - value: { type: new GraphQLNonNull(GraphQLJSONObject) }, - }, - }) - ) - } - - public generateFilterQueriesFields(name: string, type: TargetTypeMetadata): GraphQLFieldConfigArgumentMap { - const filterArguments = this.generateFilterArguments(type) - const filter: GraphQLInputObjectType = new GraphQLInputObjectType({ - name: `${name}Filter`, - fields: () => ({ - ...filterArguments, - and: { type: new GraphQLList(filter) }, - or: { type: new GraphQLList(filter) }, - not: { type: filter }, - }), - }) - return { filter: { type: filter } } - } - - public generateListedQueriesFields(name: string, type: TargetTypeMetadata): GraphQLFieldConfigArgumentMap { - const filterArguments = this.generateFilterArguments(type) - const filter: GraphQLInputObjectType = new GraphQLInputObjectType({ - name: `List${name}Filter`, - fields: () => ({ - ...filterArguments, - and: { type: new GraphQLList(filter) }, - or: { type: new GraphQLList(filter) }, - not: { type: filter }, - }), - }) - return { - filter: { type: filter }, - limit: { type: GraphQLInt }, - afterCursor: { type: GraphQLJSONObject }, - } - } - - public generateFilterArguments(typeMetadata: TargetTypeMetadata): GraphQLFieldConfigArgumentMap { - const args: GraphQLFieldConfigArgumentMap = {} - typeMetadata.properties.forEach((prop: PropertyMetadata) => { - args[prop.name] = { - type: this.generateFilterFor(prop), - } - }) - return args - } - - private generateArrayFilterFor(property: PropertyMetadata): GraphQLInputObjectType { - const filterName = `${property.name}PropertyFilter` - - if (!this.generatedFiltersByTypeName[filterName]) { - const propFilters: GraphQLInputFieldConfigMap = {} - property.typeInfo.parameters.forEach((param) => { - const primitiveType = this.typeInformer.getOriginalAncestor(param.type) - let graphqlType: GraphQLScalarType - switch (primitiveType) { - case Boolean: - graphqlType = GraphQLBoolean - break - case String: - graphqlType = GraphQLString - break - case Number: - graphqlType = GraphQLFloat - break - default: - graphqlType = GraphQLJSONObject - break - } - propFilters.includes = { type: graphqlType } - }) - - this.generatedFiltersByTypeName[filterName] = new GraphQLInputObjectType({ - name: filterName, - fields: propFilters, - }) - } - return this.generatedFiltersByTypeName[filterName] - } - - private generateFilterFor(prop: PropertyMetadata): GraphQLInputObjectType | GraphQLScalarType { - const filterName = `${prop.typeInfo.name}PropertyFilter` - - if (!prop.typeInfo.type || typeof prop.typeInfo.type === 'object') return GraphQLJSONObject - - if (!this.generatedFiltersByTypeName[filterName]) { - const primitiveType = this.typeInformer.getOriginalAncestor(prop.typeInfo.type) - if (primitiveType === Array) return this.generateArrayFilterFor(prop) - const graphQLPropType = this.typeInformer.getGraphQLTypeFor(primitiveType) - let fields: Thunk = {} - - if (!this.typeInformer.isGraphQLScalarType(graphQLPropType)) { - let nestedProperties: GraphQLInputFieldConfigMap = {} - const properties = getPropertiesMetadata(prop.typeInfo.type) - if (properties.length > 0) { - this.typeInformer.generateGraphQLTypeFromMetadata({ class: prop.typeInfo.type, properties }) - - for (const prop of properties) { - const property = { [prop.name]: { type: this.generateFilterFor(prop) } } - nestedProperties = { ...nestedProperties, ...property } - } - } else { - return GraphQLJSONObject - } - fields = () => ({ - ...nestedProperties, - and: { type: new GraphQLList(this.generatedFiltersByTypeName[filterName]) }, - or: { type: new GraphQLList(this.generatedFiltersByTypeName[filterName]) }, - not: { type: this.generatedFiltersByTypeName[filterName] }, - }) - } else { - fields = this.generateFilterInputTypes(prop.typeInfo.type) - } - this.generatedFiltersByTypeName[filterName] = new GraphQLInputObjectType({ name: filterName, fields }) - } - return this.generatedFiltersByTypeName[filterName] - } - - private generateFilterInputTypes(type: AnyClass): GraphQLInputFieldConfigMap { - const primitiveType = this.typeInformer.getOriginalAncestor(type) - switch (primitiveType) { - case Boolean: - return { - eq: { type: GraphQLBoolean }, - ne: { type: GraphQLBoolean }, - } - case Number: - return { - eq: { type: GraphQLFloat }, - ne: { type: GraphQLFloat }, - lte: { type: GraphQLFloat }, - lt: { type: GraphQLFloat }, - gte: { type: GraphQLFloat }, - gt: { type: GraphQLFloat }, - in: { type: GraphQLList(GraphQLFloat) }, - } - case String: - return { - eq: { type: GraphQLString }, - ne: { type: GraphQLString }, - lte: { type: GraphQLString }, - lt: { type: GraphQLString }, - gte: { type: GraphQLString }, - gt: { type: GraphQLString }, - in: { type: GraphQLList(GraphQLString) }, - beginsWith: { type: GraphQLString }, - contains: { type: GraphQLString }, - } - default: - throw new Error(`Type ${type.name} is not supported in search filters`) - } - } - - private buildGraphqlSimpleEnumFor(enumName: string, values: Array): GraphQLEnumType { - return new GraphQLEnumType({ - name: enumName, - values: values.reduce((valuesRecord, value) => { - valuesRecord[value] = { value } - return valuesRecord - }, {} as GraphQLEnumValueConfigMap), - }) - } } diff --git a/packages/framework-core/src/services/graphql/graphql-subcriptions-generator.ts b/packages/framework-core/src/services/graphql/graphql-subcriptions-generator.ts index 34326bb0d..c3171bb36 100644 --- a/packages/framework-core/src/services/graphql/graphql-subcriptions-generator.ts +++ b/packages/framework-core/src/services/graphql/graphql-subcriptions-generator.ts @@ -1,17 +1,21 @@ -import { GraphQLFieldConfigMap, GraphQLID, GraphQLNonNull, GraphQLObjectType } from 'graphql' +import { GraphQLFieldConfigMap, GraphQLID, GraphQLInputObjectType, GraphQLNonNull, GraphQLObjectType } from 'graphql' import { ResolverBuilder, TargetTypesMap } from './common' import { GraphQLTypeInformer } from './graphql-type-informer' -import { GraphQLQueryGenerator } from './graphql-query-generator' import * as inflected from 'inflected' +import { GraphqlQueryFilterFieldsBuilder } from './query-helpers/graphql-query-filter-fields-builder' export class GraphQLSubscriptionGenerator { + private graphqlQueryFilterFieldsBuilder: GraphqlQueryFilterFieldsBuilder + public constructor( private readonly targetTypes: TargetTypesMap, private readonly typeInformer: GraphQLTypeInformer, - private readonly queryGenerator: GraphQLQueryGenerator, private readonly byIDResolverBuilder: ResolverBuilder, - private readonly filterResolverBuilder: ResolverBuilder - ) {} + private readonly filterResolverBuilder: ResolverBuilder, + protected generatedFiltersByTypeName: Record = {} + ) { + this.graphqlQueryFilterFieldsBuilder = new GraphqlQueryFilterFieldsBuilder(typeInformer, generatedFiltersByTypeName) + } public generate(): GraphQLObjectType | undefined { const byIDSubscriptions = this.generateByIDSubscriptions() @@ -50,7 +54,7 @@ export class GraphQLSubscriptionGenerator { const graphQLType = this.typeInformer.getGraphQLTypeFor(type.class) subscriptions[inflected.pluralize(name)] = { type: graphQLType, - args: this.queryGenerator.generateFilterQueriesFields(`${name}Subscription`, type), + args: this.graphqlQueryFilterFieldsBuilder.generateFilterQueriesFields(`${name}Subscription`, type), resolve: (source) => source, subscribe: this.filterResolverBuilder(type.class), } diff --git a/packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts b/packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts new file mode 100644 index 000000000..aea738898 --- /dev/null +++ b/packages/framework-core/src/services/graphql/query-generators/graphql-query-by-keys-generator.ts @@ -0,0 +1,54 @@ +import { GraphQLFieldConfig, GraphQLFieldConfigMap, GraphQLID, GraphQLList, GraphQLNonNull } from 'graphql' +import { GraphQLResolverContext, ResolverBuilder, TargetTypesMap } from '../common' +import { GraphQLTypeInformer } from '../graphql-type-informer' +import { BoosterConfig } from '@boostercloud/framework-types' + +export class GraphqlQueryByKeysGenerator { + public constructor( + private readonly config: BoosterConfig, + private readonly readModelsMetadata: TargetTypesMap, + private readonly typeInformer: GraphQLTypeInformer, + private readonly byIDResolverBuilder: ResolverBuilder + ) {} + + public generateByKeysQueries(): GraphQLFieldConfigMap { + const queries: GraphQLFieldConfigMap = {} + for (const readModelName in this.readModelsMetadata) { + const sequenceKeyName = this.config.readModelSequenceKeys[readModelName] + if (sequenceKeyName) { + queries[readModelName] = this.generateByIdAndSequenceKeyQuery(readModelName, sequenceKeyName) + } else { + queries[readModelName] = this.generateByIdQuery(readModelName) + } + } + return queries + } + + private generateByIdQuery(readModelName: string): GraphQLFieldConfig { + const readModelMetadata = this.readModelsMetadata[readModelName] + const graphQLType = this.typeInformer.getGraphQLTypeFor(readModelMetadata.class) + return { + type: graphQLType, + args: { + id: { type: new GraphQLNonNull(GraphQLID) }, + }, + resolve: this.byIDResolverBuilder(readModelMetadata.class), + } + } + + private generateByIdAndSequenceKeyQuery( + readModelName: string, + sequenceKeyName: string + ): GraphQLFieldConfig { + const readModelMetadata = this.readModelsMetadata[readModelName] + const graphQLType = this.typeInformer.getGraphQLTypeFor(readModelMetadata.class) + return { + type: new GraphQLList(graphQLType), + args: { + id: { type: new GraphQLNonNull(GraphQLID) }, + [sequenceKeyName]: { type: GraphQLID }, + }, + resolve: this.byIDResolverBuilder(readModelMetadata.class), + } + } +} diff --git a/packages/framework-core/src/services/graphql/query-generators/graphql-query-events-generator.ts b/packages/framework-core/src/services/graphql/query-generators/graphql-query-events-generator.ts new file mode 100644 index 000000000..a529c9095 --- /dev/null +++ b/packages/framework-core/src/services/graphql/query-generators/graphql-query-events-generator.ts @@ -0,0 +1,79 @@ +import { + GraphQLFieldConfigMap, + GraphQLFieldResolver, + GraphQLID, + GraphQLInt, + GraphQLList, + GraphQLNonNull, + GraphQLObjectType, + GraphQLOutputType, + GraphQLString, +} from 'graphql' +import { buildGraphqlSimpleEnumFor, GraphQLResolverContext, ResolverBuilder } from '../common' +import { GraphQLJSONObject } from 'graphql-type-json' +import { BoosterConfig } from '@boostercloud/framework-types' + +export class GraphqlQueryEventsGenerator { + constructor( + private readonly config: BoosterConfig, + protected readonly byIDResolverBuilder: ResolverBuilder, + private readonly eventsResolver: GraphQLFieldResolver + ) {} + + public generateEventQueries(): GraphQLFieldConfigMap { + const eventQueryResponse = GraphqlQueryEventsGenerator.buildEventQueryResponse() + return { + eventsByEntity: { + type: eventQueryResponse, + args: { + entity: { + type: new GraphQLNonNull(buildGraphqlSimpleEnumFor('EntityType', Object.keys(this.config.entities))), + }, + entityID: { type: GraphQLID }, + from: { type: GraphQLString }, + to: { type: GraphQLString }, + limit: { type: GraphQLInt }, + }, + resolve: this.eventsResolver, + }, + eventsByType: { + type: eventQueryResponse, + args: { + type: { + type: new GraphQLNonNull(buildGraphqlSimpleEnumFor('EventType', Object.keys(this.config.reducers))), + }, + from: { type: GraphQLString }, + to: { type: GraphQLString }, + limit: { type: GraphQLInt }, + }, + resolve: this.eventsResolver, + }, + } + } + + private static buildEventQueryResponse(): GraphQLOutputType { + return new GraphQLList( + new GraphQLObjectType({ + name: 'EventQueryResponse', + fields: { + type: { type: new GraphQLNonNull(GraphQLString) }, + entity: { type: new GraphQLNonNull(GraphQLString) }, + entityID: { type: new GraphQLNonNull(GraphQLID) }, + requestID: { type: new GraphQLNonNull(GraphQLID) }, + user: { + type: new GraphQLObjectType({ + name: 'User', + fields: { + id: { type: GraphQLString }, + username: { type: new GraphQLNonNull(GraphQLString) }, + roles: { type: new GraphQLList(new GraphQLNonNull(GraphQLString)) }, + }, + }), + }, + createdAt: { type: new GraphQLNonNull(GraphQLString) }, + value: { type: new GraphQLNonNull(GraphQLJSONObject) }, + }, + }) + ) + } +} diff --git a/packages/framework-core/src/services/graphql/query-generators/graphql-query-filters-generator.ts b/packages/framework-core/src/services/graphql/query-generators/graphql-query-filters-generator.ts new file mode 100644 index 000000000..62accf7d2 --- /dev/null +++ b/packages/framework-core/src/services/graphql/query-generators/graphql-query-filters-generator.ts @@ -0,0 +1,33 @@ +import { GraphQLFieldConfigMap, GraphQLInputObjectType, GraphQLList } from 'graphql' +import { GraphQLResolverContext, ResolverBuilder, TargetTypesMap } from '../common' +import * as inflected from 'inflected' +import { GraphQLTypeInformer } from '../graphql-type-informer' +import { GraphqlQueryFilterFieldsBuilder } from '../query-helpers/graphql-query-filter-fields-builder' + +export class GraphqlQueryFiltersGenerator { + private graphqlQueryFilterFieldsBuilder: GraphqlQueryFilterFieldsBuilder + + constructor( + private readonly readModelsMetadata: TargetTypesMap, + private readonly typeInformer: GraphQLTypeInformer, + private readonly filterResolverBuilder: ResolverBuilder, + protected generatedFiltersByTypeName: Record = {} + ) { + this.graphqlQueryFilterFieldsBuilder = new GraphqlQueryFilterFieldsBuilder(typeInformer, generatedFiltersByTypeName) + } + + public generateFilterQueries(): GraphQLFieldConfigMap { + const queries: GraphQLFieldConfigMap = {} + for (const name in this.readModelsMetadata) { + const type = this.readModelsMetadata[name] + const graphQLType = this.typeInformer.getGraphQLTypeFor(type.class) + queries[inflected.pluralize(name)] = { + type: new GraphQLList(graphQLType), + args: this.graphqlQueryFilterFieldsBuilder.generateFilterQueriesFields(name, type), + resolve: this.filterResolverBuilder(type.class), + deprecationReason: 'Method is deprecated. Use List* methods', + } + } + return queries + } +} diff --git a/packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts b/packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts new file mode 100644 index 000000000..30e0e0639 --- /dev/null +++ b/packages/framework-core/src/services/graphql/query-generators/graphql-query-listed-generator.ts @@ -0,0 +1,78 @@ +import { + GraphQLFieldConfigArgumentMap, + GraphQLFieldConfigMap, + GraphQLInputObjectType, + GraphQLInt, + GraphQLList, + GraphQLObjectType, +} from 'graphql' +import { GraphQLResolverContext, ResolverBuilder, TargetTypeMetadata, TargetTypesMap } from '../common' +import * as inflected from 'inflected' +import { GraphQLJSONObject } from 'graphql-type-json' +import { GraphQLTypeInformer } from '../graphql-type-informer' +import { GraphqlQuerySortBuilder } from '../query-helpers/graphql-query-sort-builder' +import { GraphqlQueryFilterArgumentsBuilder } from '../query-helpers/graphql-query-filter-arguments-builder' + +export class GraphqlQueryListedGenerator { + private graphqlQueryFilterArgumentsBuilder: GraphqlQueryFilterArgumentsBuilder + private graphqlQuerySortBuilder: GraphqlQuerySortBuilder + + constructor( + private readonly readModelsMetadata: TargetTypesMap, + private readonly typeInformer: GraphQLTypeInformer, + private readonly filterResolverBuilder: ResolverBuilder, + protected generatedFiltersByTypeName: Record = {} + ) { + this.graphqlQueryFilterArgumentsBuilder = new GraphqlQueryFilterArgumentsBuilder( + typeInformer, + generatedFiltersByTypeName + ) + this.graphqlQuerySortBuilder = new GraphqlQuerySortBuilder(typeInformer) + } + + public generateListedQueries(): GraphQLFieldConfigMap { + const queries: GraphQLFieldConfigMap = {} + for (const name in this.readModelsMetadata) { + const type = this.readModelsMetadata[name] + const graphQLType = this.typeInformer.getGraphQLTypeFor(type.class) + queries[`List${inflected.pluralize(name)}`] = { + type: new GraphQLObjectType({ + name: `${name}Connection`, + fields: { + items: { type: new GraphQLList(graphQLType) }, + cursor: { type: GraphQLJSONObject }, + }, + }), + args: this.generateListedQueriesFields(name, type), + resolve: this.filterResolverBuilder(type.class), + } + } + return queries + } + + public generateListedQueriesFields(name: string, type: TargetTypeMetadata): GraphQLFieldConfigArgumentMap { + const filterArguments = this.graphqlQueryFilterArgumentsBuilder.generateFilterArguments(type) + const filter: GraphQLInputObjectType = new GraphQLInputObjectType({ + name: `List${name}Filter`, + fields: () => ({ + ...filterArguments, + and: { type: new GraphQLList(filter) }, + or: { type: new GraphQLList(filter) }, + not: { type: filter }, + }), + }) + const sortArguments = this.graphqlQuerySortBuilder.generateSortArguments(type) + const sort: GraphQLInputObjectType = new GraphQLInputObjectType({ + name: `${name}SortBy`, + fields: () => ({ + ...sortArguments, + }), + }) + return { + filter: { type: filter }, + limit: { type: GraphQLInt }, + sortBy: { type: sort }, + afterCursor: { type: GraphQLJSONObject }, + } + } +} diff --git a/packages/framework-core/src/services/graphql/query-helpers/graphql-query-filter-arguments-builder.ts b/packages/framework-core/src/services/graphql/query-helpers/graphql-query-filter-arguments-builder.ts new file mode 100644 index 000000000..007100ba7 --- /dev/null +++ b/packages/framework-core/src/services/graphql/query-helpers/graphql-query-filter-arguments-builder.ts @@ -0,0 +1,140 @@ +import { TargetTypeMetadata } from '../common' +import { + GraphQLBoolean, + GraphQLFieldConfigArgumentMap, + GraphQLFloat, + GraphQLInputFieldConfigMap, + GraphQLInputObjectType, + GraphQLList, + GraphQLScalarType, + GraphQLString, + Thunk, +} from 'graphql' +import { getPropertiesMetadata } from '../../../decorators/metadata' +import { PropertyMetadata } from 'metadata-booster' +import { GraphQLJSONObject } from 'graphql-type-json' +import { AnyClass } from '@boostercloud/framework-types' +import { GraphQLTypeInformer } from '../graphql-type-informer' + +export class GraphqlQueryFilterArgumentsBuilder { + constructor( + private readonly typeInformer: GraphQLTypeInformer, + protected generatedFiltersByTypeName: Record = {} + ) {} + + public generateFilterArguments(typeMetadata: TargetTypeMetadata): GraphQLFieldConfigArgumentMap { + const args: GraphQLFieldConfigArgumentMap = {} + typeMetadata.properties.forEach((prop: PropertyMetadata) => { + args[prop.name] = { + type: this.generateFilterFor(prop), + } + }) + return args + } + + private generateFilterFor(prop: PropertyMetadata): GraphQLInputObjectType | GraphQLScalarType { + const filterName = `${prop.typeInfo.name}PropertyFilter` + + if (!prop.typeInfo.type || typeof prop.typeInfo.type === 'object') return GraphQLJSONObject + + if (!this.generatedFiltersByTypeName[filterName]) { + const primitiveType = this.typeInformer.getOriginalAncestor(prop.typeInfo.type) + if (primitiveType === Array) return this.generateArrayFilterFor(prop) + const graphQLPropType = this.typeInformer.getGraphQLTypeFor(primitiveType) + let fields: Thunk = {} + + if (!this.typeInformer.isGraphQLScalarType(graphQLPropType)) { + let nestedProperties: GraphQLInputFieldConfigMap = {} + const properties = getPropertiesMetadata(prop.typeInfo.type) + if (properties.length > 0) { + this.typeInformer.generateGraphQLTypeFromMetadata({ class: prop.typeInfo.type, properties }) + + for (const prop of properties) { + const property = { [prop.name]: { type: this.generateFilterFor(prop) } } + nestedProperties = { ...nestedProperties, ...property } + } + } else { + return GraphQLJSONObject + } + fields = () => ({ + ...nestedProperties, + and: { type: new GraphQLList(this.generatedFiltersByTypeName[filterName]) }, + or: { type: new GraphQLList(this.generatedFiltersByTypeName[filterName]) }, + not: { type: this.generatedFiltersByTypeName[filterName] }, + }) + } else { + fields = this.generateFilterInputTypes(prop.typeInfo.type) + } + this.generatedFiltersByTypeName[filterName] = new GraphQLInputObjectType({ name: filterName, fields }) + } + return this.generatedFiltersByTypeName[filterName] + } + + private generateArrayFilterFor(property: PropertyMetadata): GraphQLInputObjectType { + const filterName = `${property.name}PropertyFilter` + + if (!this.generatedFiltersByTypeName[filterName]) { + const propFilters: GraphQLInputFieldConfigMap = {} + property.typeInfo.parameters.forEach((param) => { + const primitiveType = this.typeInformer.getOriginalAncestor(param.type) + let graphqlType: GraphQLScalarType + switch (primitiveType) { + case Boolean: + graphqlType = GraphQLBoolean + break + case String: + graphqlType = GraphQLString + break + case Number: + graphqlType = GraphQLFloat + break + default: + graphqlType = GraphQLJSONObject + break + } + propFilters.includes = { type: graphqlType } + }) + + this.generatedFiltersByTypeName[filterName] = new GraphQLInputObjectType({ + name: filterName, + fields: propFilters, + }) + } + return this.generatedFiltersByTypeName[filterName] + } + + private generateFilterInputTypes(type: AnyClass): GraphQLInputFieldConfigMap { + const primitiveType = this.typeInformer.getOriginalAncestor(type) + switch (primitiveType) { + case Boolean: + return { + eq: { type: GraphQLBoolean }, + ne: { type: GraphQLBoolean }, + } + case Number: + return { + eq: { type: GraphQLFloat }, + ne: { type: GraphQLFloat }, + lte: { type: GraphQLFloat }, + lt: { type: GraphQLFloat }, + gte: { type: GraphQLFloat }, + gt: { type: GraphQLFloat }, + in: { type: GraphQLList(GraphQLFloat) }, + } + case String: + return { + eq: { type: GraphQLString }, + ne: { type: GraphQLString }, + lte: { type: GraphQLString }, + lt: { type: GraphQLString }, + gte: { type: GraphQLString }, + gt: { type: GraphQLString }, + in: { type: GraphQLList(GraphQLString) }, + beginsWith: { type: GraphQLString }, + contains: { type: GraphQLString }, + } + default: + throw new Error(`Type ${type.name} is not supported in search filters`) + } + } +} diff --git a/packages/framework-core/src/services/graphql/query-helpers/graphql-query-filter-fields-builder.ts b/packages/framework-core/src/services/graphql/query-helpers/graphql-query-filter-fields-builder.ts new file mode 100644 index 000000000..249bbaf0b --- /dev/null +++ b/packages/framework-core/src/services/graphql/query-helpers/graphql-query-filter-fields-builder.ts @@ -0,0 +1,32 @@ +import { TargetTypeMetadata } from '../common' +import { GraphQLFieldConfigArgumentMap, GraphQLInputObjectType, GraphQLList } from 'graphql' +import { GraphQLTypeInformer } from '../graphql-type-informer' +import { GraphqlQueryFilterArgumentsBuilder } from './graphql-query-filter-arguments-builder' + +export class GraphqlQueryFilterFieldsBuilder { + private graphqlQueryFilterArgumentsBuilder: GraphqlQueryFilterArgumentsBuilder + + constructor( + protected readonly typeInformer: GraphQLTypeInformer, + protected generatedFiltersByTypeName: Record = {} + ) { + this.graphqlQueryFilterArgumentsBuilder = new GraphqlQueryFilterArgumentsBuilder( + typeInformer, + generatedFiltersByTypeName + ) + } + + public generateFilterQueriesFields(name: string, type: TargetTypeMetadata): GraphQLFieldConfigArgumentMap { + const filterArguments = this.graphqlQueryFilterArgumentsBuilder.generateFilterArguments(type) + const filter: GraphQLInputObjectType = new GraphQLInputObjectType({ + name: `${name}Filter`, + fields: () => ({ + ...filterArguments, + and: { type: new GraphQLList(filter) }, + or: { type: new GraphQLList(filter) }, + not: { type: filter }, + }), + }) + return { filter: { type: filter } } + } +} diff --git a/packages/framework-core/src/services/graphql/query-helpers/graphql-query-sort-builder.ts b/packages/framework-core/src/services/graphql/query-helpers/graphql-query-sort-builder.ts new file mode 100644 index 000000000..5f229871a --- /dev/null +++ b/packages/framework-core/src/services/graphql/query-helpers/graphql-query-sort-builder.ts @@ -0,0 +1,56 @@ +import { GraphQLEnumType, GraphQLFieldConfigArgumentMap, GraphQLInputObjectType, Thunk } from 'graphql' +import { PropertyMetadata } from 'metadata-booster' +import { getPropertiesMetadata } from '../../../decorators/metadata' +import { buildGraphqlSimpleEnumFor, TargetTypeMetadata } from '../common' +import { GraphQLInputFieldConfigMap } from 'graphql/type/definition' +import { GraphQLTypeInformer } from '../graphql-type-informer' + +export class GraphqlQuerySortBuilder { + private generatedSortByByTypeName: Record = {} + private orderType = buildGraphqlSimpleEnumFor('orderProperty', ['ASC', 'DESC']) + + constructor(protected readonly typeInformer: GraphQLTypeInformer) {} + + public generateSortArguments(typeMetadata: TargetTypeMetadata): GraphQLFieldConfigArgumentMap { + const args: GraphQLFieldConfigArgumentMap = {} + typeMetadata.properties.forEach((prop: PropertyMetadata) => { + args[prop.name] = { + type: this.generateSortFor(prop), + } + }) + return args + } + + private generateSortFor(prop: PropertyMetadata): GraphQLInputObjectType | GraphQLEnumType { + let sortByName = `${prop.typeInfo.name}PropertySortBy` + sortByName = sortByName.charAt(0).toUpperCase() + sortByName.substr(1).replace(/\[]/g, '') + + if (!prop.typeInfo.type || typeof prop.typeInfo.type === 'object') return this.orderType + if (this.generatedSortByByTypeName[sortByName]) return this.generatedSortByByTypeName[sortByName] + + const primitiveType = this.typeInformer.getOriginalAncestor(prop.typeInfo.type) + if (primitiveType === Array) return this.orderType + const graphQLPropType = this.typeInformer.getGraphQLTypeFor(primitiveType) + let fields: Thunk = {} + + if (!this.typeInformer.isGraphQLScalarType(graphQLPropType)) { + let nestedProperties: GraphQLInputFieldConfigMap = {} + const properties = getPropertiesMetadata(prop.typeInfo.type) + if (properties.length === 0) return this.orderType + + this.typeInformer.generateGraphQLTypeFromMetadata({ class: prop.typeInfo.type, properties }) + + for (const prop of properties) { + const property = { [prop.name]: { type: this.generateSortFor(prop) } } + nestedProperties = { ...nestedProperties, ...property } + } + fields = () => ({ + ...nestedProperties, + }) + this.generatedSortByByTypeName[sortByName] = new GraphQLInputObjectType({ name: sortByName, fields }) + return this.generatedSortByByTypeName[sortByName] + } + + return this.orderType + } +} diff --git a/packages/framework-core/test/booster-read-model-reader.test.ts b/packages/framework-core/test/booster-read-model-reader.test.ts index ccac69784..4dc9e57f5 100644 --- a/packages/framework-core/test/booster-read-model-reader.test.ts +++ b/packages/framework-core/test/booster-read-model-reader.test.ts @@ -310,6 +310,7 @@ describe('BoosterReadModelReader', () => { match.any, TestReadModel.name, filters, + {}, undefined, undefined, false diff --git a/packages/framework-core/test/booster.test.ts b/packages/framework-core/test/booster.test.ts index a424c5f17..954ebd73c 100644 --- a/packages/framework-core/test/booster.test.ts +++ b/packages/framework-core/test/booster.test.ts @@ -79,6 +79,7 @@ describe('the `Booster` class', () => { match.any, TestReadModel.name, match.any, + {}, undefined, undefined, false diff --git a/packages/framework-core/test/services/graphql/graphql-generator.test.ts b/packages/framework-core/test/services/graphql/graphql-generator.test.ts index 94d65aa32..c8edc8037 100644 --- a/packages/framework-core/test/services/graphql/graphql-generator.test.ts +++ b/packages/framework-core/test/services/graphql/graphql-generator.test.ts @@ -180,6 +180,7 @@ describe('GraphQL generator', () => { claims: {}, }, filters: {}, + sortBy: {}, requestID: mockRequestId, class: mockType, className: mockType.name, @@ -438,6 +439,7 @@ describe('GraphQL generator', () => { claims: {}, }, filters: {}, + sortBy: {}, requestID: mockRequestId, class: mockType, className: mockType.name, diff --git a/packages/framework-core/test/services/graphql/graphql-query-generator.test.ts b/packages/framework-core/test/services/graphql/graphql-query-generator.test.ts index e040fa1e2..4f0f30ebf 100644 --- a/packages/framework-core/test/services/graphql/graphql-query-generator.test.ts +++ b/packages/framework-core/test/services/graphql/graphql-query-generator.test.ts @@ -1,10 +1,10 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/ban-ts-comment */ import { GraphQLQueryGenerator } from '../../../src/services/graphql/graphql-query-generator' -import { SinonStub, stub, replace, SinonStubbedInstance, restore, fake } from 'sinon' +import { SinonStub, stub, replace, SinonStubbedInstance, restore } from 'sinon' import { expect } from '../../expect' import { GraphQLTypeInformer } from '../../../src/services/graphql/graphql-type-informer' import sinon = require('sinon') -import { GraphQLResolverContext, TargetTypesMap } from '../../../src/services/graphql/common' +import { TargetTypesMap } from '../../../src/services/graphql/common' import { GraphQLBoolean, GraphQLEnumType, @@ -14,23 +14,12 @@ import { GraphQLNonNull, GraphQLOutputType, GraphQLString, - GraphQLFieldConfigMap, GraphQLList, GraphQLObjectType, } from 'graphql' import { random } from 'faker' import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json' -import { BoosterConfig, UUID, TimeKey } from '@boostercloud/framework-types' - -function buildFakeGraphQLFielConfigMap(name: string): GraphQLFieldConfigMap { - return { - [name]: { - type: GraphQLString, - args: {}, - resolve: fake(), - }, - } -} +import { BoosterConfig } from '@boostercloud/framework-types' describe('GraphQLQueryGenerator', () => { afterEach(() => { @@ -40,96 +29,6 @@ describe('GraphQLQueryGenerator', () => { describe('the `generate` public method', () => { const simpleConfig = new BoosterConfig('test') - class SomeReadModel {} - - const fakeReadModelsMetadata = { - SomeReadModel: { - class: SomeReadModel, - properties: [], - }, - } - - const typeInformer = new GraphQLTypeInformer({ - ...fakeReadModelsMetadata, - }) - - const graphQLQueryGenerator = new GraphQLQueryGenerator( - simpleConfig, - fakeReadModelsMetadata, - typeInformer, - () => fake(), - () => fake(), - fake() - ) - - it('generates by Key queries', () => { - const fakegenerateByKeysQueries = fake() - replace(graphQLQueryGenerator as any, 'generateByKeysQueries', fakegenerateByKeysQueries) - replace(graphQLQueryGenerator as any, 'generateFilterQueries', fake()) - replace(graphQLQueryGenerator as any, 'generateListedQueries', fake()) - replace(graphQLQueryGenerator as any, 'generateEventQueries', fake()) - - graphQLQueryGenerator.generate() - - expect(fakegenerateByKeysQueries).to.have.been.calledOnce - }) - - it('generates filter queries', () => { - replace(graphQLQueryGenerator as any, 'generateByKeysQueries', fake()) - const fakeGenerateFilterQueries = fake() - replace(graphQLQueryGenerator as any, 'generateFilterQueries', fakeGenerateFilterQueries) - replace(graphQLQueryGenerator as any, 'generateListedQueries', fake()) - replace(graphQLQueryGenerator as any, 'generateEventQueries', fake()) - - graphQLQueryGenerator.generate() - - expect(fakeGenerateFilterQueries).to.have.been.calledOnce - }) - - it('generates listed queries', () => { - replace(graphQLQueryGenerator as any, 'generateByKeysQueries', fake()) - replace(graphQLQueryGenerator as any, 'generateFilterQueries', fake()) - const fakeGenerateListedQueries = fake() - replace(graphQLQueryGenerator as any, 'generateListedQueries', fakeGenerateListedQueries) - replace(graphQLQueryGenerator as any, 'generateEventQueries', fake()) - - graphQLQueryGenerator.generate() - - expect(fakeGenerateListedQueries).to.have.been.calledOnce - }) - - it('generates event queries', () => { - replace(graphQLQueryGenerator as any, 'generateByKeysQueries', fake()) - replace(graphQLQueryGenerator as any, 'generateFilterQueries', fake()) - replace(graphQLQueryGenerator as any, 'generateListedQueries', fake()) - const fakeGenerateEventQueries = fake() - replace(graphQLQueryGenerator as any, 'generateEventQueries', fakeGenerateEventQueries) - - graphQLQueryGenerator.generate() - - expect(fakeGenerateEventQueries).to.have.been.calledOnce - }) - - it('returns a well-formed GraphQL Query Object Type', () => { - const fakeIDQueries = buildFakeGraphQLFielConfigMap('IDQuery') - replace(graphQLQueryGenerator as any, 'generateByKeysQueries', fake.returns(fakeIDQueries)) - const fakeFilterQueries = buildFakeGraphQLFielConfigMap('FilterQuery') - replace(graphQLQueryGenerator as any, 'generateFilterQueries', fake.returns(fakeFilterQueries)) - const fakeListedQueries = buildFakeGraphQLFielConfigMap('ListedQuery') - replace(graphQLQueryGenerator as any, 'generateListedQueries', fake.returns(fakeListedQueries)) - const fakeEventQueries = buildFakeGraphQLFielConfigMap('EventQuery') - replace(graphQLQueryGenerator as any, 'generateEventQueries', fake.returns(fakeEventQueries)) - - const result = graphQLQueryGenerator.generate() - - expect(result).to.have.property('name', 'Query') - const fieldNames = Object.keys(result.getFields()) - expect(fieldNames).to.include('IDQuery') - expect(fieldNames).to.include('FilterQuery') - expect(fieldNames).to.include('ListedQuery') - expect(fieldNames).to.include('EventQuery') - }) - context('black box tests', () => { let mockTargetTypes: TargetTypesMap let mockGraphQLType: any @@ -168,7 +67,7 @@ describe('GraphQLQueryGenerator', () => { context('1 target type', () => { let mockTargetTypeClass: BooleanConstructor | StringConstructor | NumberConstructor let mockTargetTypeName: string - let sut: GraphQLQueryGenerator + let graphQLQueryGenerator: GraphQLQueryGenerator beforeEach(() => { mockTargetTypeClass = random.arrayElement([Boolean, String, Number]) @@ -181,7 +80,7 @@ describe('GraphQLQueryGenerator', () => { properties: [], } - sut = new GraphQLQueryGenerator( + graphQLQueryGenerator = new GraphQLQueryGenerator( simpleConfig, mockTargetTypes, mockTypeInformer as any, @@ -192,13 +91,13 @@ describe('GraphQLQueryGenerator', () => { }) it('should call typeInformer.getGraphQLTypeFor thrice', () => { - sut.generate() + graphQLQueryGenerator.generate() expect(getGraphQLTypeForStub).calledThrice.and.calledWith(mockTargetTypeClass) }) it('should call filterResolverBuilder once with expected argument', () => { - sut.generate() + graphQLQueryGenerator.generate() expect(mockByIdResolverBuilder).calledOnce.and.calledWith(mockTargetTypeClass) // @ts-ignore @@ -206,7 +105,7 @@ describe('GraphQLQueryGenerator', () => { }) it('should return expected result', () => { - const result = sut.generate() + const result = graphQLQueryGenerator.generate() expect(result.name).to.be.equal('Query') expect(result.description).to.be.undefined @@ -227,7 +126,9 @@ describe('GraphQLQueryGenerator', () => { expect(config.fields[`${mockTargetTypeName}s`].type.toString()).to.be.equal(`[${mockGraphQLType}]`) expect(config.fields[`${mockTargetTypeName}s`].resolve).to.be.undefined expect(config.fields[`${mockTargetTypeName}s`].subscribe).to.be.undefined - expect(config.fields[`${mockTargetTypeName}s`].deprecationReason).to.be.undefined + expect(config.fields[`${mockTargetTypeName}s`].deprecationReason).to.be.equal( + 'Method is deprecated. Use List* methods' + ) expect(config.fields[`${mockTargetTypeName}s`].extensions).to.be.undefined expect(config.fields[`${mockTargetTypeName}s`].astNode).to.be.undefined }) @@ -261,7 +162,7 @@ describe('GraphQLQueryGenerator', () => { context('Property GraphQL Type is scalar', () => { beforeEach(() => { - sut = new GraphQLQueryGenerator( + graphQLQueryGenerator = new GraphQLQueryGenerator( simpleConfig, mockTargetTypes, mockTypeInformer as any, @@ -271,17 +172,17 @@ describe('GraphQLQueryGenerator', () => { ) }) - it('should call typeInformer.getGraphQLTypeFor 4 times', () => { - sut.generate() + it('should call typeInformer.getGraphQLTypeFor 5 times', () => { + graphQLQueryGenerator.generate() expect(getGraphQLTypeForStub) - .callCount(4) + .callCount(5) .and.calledWith(mockTargetType) .and.calledWith(mockPropertyType) }) it('should call filterResolverBuilder once with expected arguments', () => { - sut.generate() + graphQLQueryGenerator.generate() expect(mockByIdResolverBuilder).to.be.calledOnce.and.calledWith(mockTargetType) // @ts-ignore @@ -290,7 +191,7 @@ describe('GraphQLQueryGenerator', () => { context('should have expected args', () => { it('When Boolean', () => { - const result = sut.generate() + const result = graphQLQueryGenerator.generate() const config: any = result.toConfig() expect(config.fields[mockTargetTypeName].args['id'].type.toString()).to.be.equal('ID!') @@ -314,6 +215,25 @@ describe('GraphQLQueryGenerator', () => { expect(booleansTypeFilterConfig.type.getFields()[fieldKey].extensions).to.be.undefined expect(booleansTypeFilterConfig.type.getFields()[fieldKey].astNode).to.be.undefined }) + + const sortBy = config.fields[`List${mockTargetTypeName}s`].args.sortBy + expect(sortBy.description).to.be.undefined + expect(sortBy.defaultValue).to.be.undefined + expect(sortBy.deprecationReason).to.be.undefined + expect(sortBy.extensions).to.be.undefined + expect(sortBy.astNode).to.be.undefined + const booleansTypeSortConfig = sortBy.type.getFields()[mockPropertyName] + expect(booleansTypeSortConfig.type.toString()).to.not.be.undefined + expect(sortBy.type.name.toString()).to.be.eq(`${mockTargetTypeName}SortBy`) + const sortKeys = Object.keys(sortBy.type.getFields()) + sortKeys.forEach((fieldKey) => { + expect(sortBy.type.getFields()[fieldKey].type.toString()).to.be.equal('orderProperty') + const values = sortBy.type + .getFields() + [fieldKey].type.getValues() + .map((value: { name: any }) => value.name) + expect(values).to.be.eql(['ASC', 'DESC']) + }) }) it('When Number', () => { @@ -334,7 +254,7 @@ describe('GraphQLQueryGenerator', () => { }, ], } - sut = new GraphQLQueryGenerator( + graphQLQueryGenerator = new GraphQLQueryGenerator( simpleConfig, mockTargetTypes, mockTypeInformer as any, @@ -342,7 +262,7 @@ describe('GraphQLQueryGenerator', () => { mockFilterResolverBuilder, mockEventsResolver ) - const result = sut.generate() + const result = graphQLQueryGenerator.generate() const config: any = result.toConfig() expect(config.fields[mockTargetTypeName].args['id'].type.toString()).to.be.equal('ID!') @@ -367,6 +287,25 @@ describe('GraphQLQueryGenerator', () => { expect(TypeFilterConfig.type.getFields()[fieldKey].extensions).to.be.undefined expect(TypeFilterConfig.type.getFields()[fieldKey].astNode).to.be.undefined }) + + const sortBy = config.fields[`List${mockTargetTypeName}s`].args.sortBy + expect(sortBy.description).to.be.undefined + expect(sortBy.defaultValue).to.be.undefined + expect(sortBy.deprecationReason).to.be.undefined + expect(sortBy.extensions).to.be.undefined + expect(sortBy.astNode).to.be.undefined + const booleansTypeSortConfig = sortBy.type.getFields()[mockPropertyName] + expect(booleansTypeSortConfig.type.toString()).to.not.be.undefined + expect(sortBy.type.name.toString()).to.be.eq(`${mockTargetTypeName}SortBy`) + const sortKeys = Object.keys(sortBy.type.getFields()) + sortKeys.forEach((fieldKey) => { + expect(sortBy.type.getFields()[fieldKey].type.toString()).to.be.equal('orderProperty') + const values = sortBy.type + .getFields() + [fieldKey].type.getValues() + .map((value: { name: any }) => value.name) + expect(values).to.be.eql(['ASC', 'DESC']) + }) }) it('When String', () => { @@ -387,7 +326,7 @@ describe('GraphQLQueryGenerator', () => { }, ], } - sut = new GraphQLQueryGenerator( + graphQLQueryGenerator = new GraphQLQueryGenerator( simpleConfig, mockTargetTypes, mockTypeInformer as any, @@ -395,7 +334,7 @@ describe('GraphQLQueryGenerator', () => { mockFilterResolverBuilder, mockEventsResolver ) - const result = sut.generate() + const result = graphQLQueryGenerator.generate() const config: any = result.toConfig() expect(config.fields[mockTargetTypeName].args['id'].type.toString()).to.be.equal('ID!') @@ -430,6 +369,25 @@ describe('GraphQLQueryGenerator', () => { expect(TypeFilterConfig.type.getFields()[fieldKey].extensions).to.be.undefined expect(TypeFilterConfig.type.getFields()[fieldKey].astNode).to.be.undefined }) + + const sortBy = config.fields[`List${mockTargetTypeName}s`].args.sortBy + expect(sortBy.description).to.be.undefined + expect(sortBy.defaultValue).to.be.undefined + expect(sortBy.deprecationReason).to.be.undefined + expect(sortBy.extensions).to.be.undefined + expect(sortBy.astNode).to.be.undefined + const booleansTypeSortConfig = sortBy.type.getFields()[mockPropertyName] + expect(booleansTypeSortConfig.type.toString()).to.not.be.undefined + expect(sortBy.type.name.toString()).to.be.eq(`${mockTargetTypeName}SortBy`) + const sortKeys = Object.keys(sortBy.type.getFields()) + sortKeys.forEach((fieldKey) => { + expect(sortBy.type.getFields()[fieldKey].type.toString()).to.be.equal('orderProperty') + const values = sortBy.type + .getFields() + [fieldKey].type.getValues() + .map((value: { name: any }) => value.name) + expect(values).to.be.eql(['ASC', 'DESC']) + }) }) }) }) @@ -457,7 +415,7 @@ describe('GraphQLQueryGenerator', () => { ], } - sut = new GraphQLQueryGenerator( + graphQLQueryGenerator = new GraphQLQueryGenerator( simpleConfig, mockTargetTypes, mockTypeInformer as any, @@ -467,17 +425,17 @@ describe('GraphQLQueryGenerator', () => { ) }) - it('should call typeInformer.getGraphQLTypeFor 5 times', () => { - sut.generate() + it('should call typeInformer.getGraphQLTypeFor 6 times', () => { + graphQLQueryGenerator.generate() expect(getGraphQLTypeForStub) - .callCount(5) + .callCount(6) .and.calledWith(mockTargetType) .and.calledWith(mockPropertyType) }) it('should call filterResolverBuilder once with expected arguments', () => { - sut.generate() + graphQLQueryGenerator.generate() expect(mockByIdResolverBuilder).to.be.calledOnce.and.calledWith(mockTargetType) // @ts-ignore @@ -506,7 +464,7 @@ describe('GraphQLQueryGenerator', () => { ], } - sut = new GraphQLQueryGenerator( + graphQLQueryGenerator = new GraphQLQueryGenerator( simpleConfig, mockTargetTypes, mockTypeInformer as any, @@ -516,17 +474,17 @@ describe('GraphQLQueryGenerator', () => { ) }) - it('should call typeInformer.getGraphQLTypeFor 4 times', () => { - sut.generate() + it('should call typeInformer.getGraphQLTypeFor 5 times', () => { + graphQLQueryGenerator.generate() expect(getGraphQLTypeForStub) - .callCount(4) + .callCount(5) .and.calledWith(mockTargetType) .and.calledWith(mockPropertyType) }) it('should call filterResolverBuilder once with expected arguments', () => { - sut.generate() + graphQLQueryGenerator.generate() expect(mockByIdResolverBuilder).to.be.calledOnce.and.calledWith(mockTargetType) // @ts-ignore @@ -709,137 +667,6 @@ describe('GraphQLQueryGenerator', () => { }) }) - describe('the `generateByKeysQueries` private method', () => { - class AnotherReadModel { - public constructor(readonly id: UUID, readonly otherField: string) {} - } - - class ASequencedReadModel { - public constructor(readonly id: UUID, readonly timestamp: TimeKey) {} - } - - const fakeReadModelsMetadata: TargetTypesMap = { - AnotherReadModel: { - class: AnotherReadModel, - properties: [], - }, - ASequencedReadModel: { - class: ASequencedReadModel, - properties: [], - }, - } - - const typeInformer = new GraphQLTypeInformer({ - ...fakeReadModelsMetadata, - }) - - const config = new BoosterConfig('test') - config.readModelSequenceKeys['ASequencedReadModel'] = 'timestamp' - - const graphQLQueryGenerator = new GraphQLQueryGenerator( - config, - fakeReadModelsMetadata, - typeInformer, - () => fake(), - () => fake(), - fake() - ) as any // So we can see private methods - - it('generates by ID and sequenced queries', () => { - const fakeGenerateByIdQuery = fake() - replace(graphQLQueryGenerator, 'generateByIdQuery', fakeGenerateByIdQuery) - const fakeGenerateByIdAndSequenceKeyQuery = fake() - replace(graphQLQueryGenerator, 'generateByIdAndSequenceKeyQuery', fakeGenerateByIdAndSequenceKeyQuery) - - graphQLQueryGenerator.generateByKeysQueries() - - expect(fakeGenerateByIdQuery).to.have.been.calledOnceWith('AnotherReadModel') - expect(fakeGenerateByIdAndSequenceKeyQuery).to.have.been.calledOnceWith('ASequencedReadModel', 'timestamp') - }) - }) - - describe('the `generateByIdQuery` private method', () => { - class ARegularReadModel { - readonly id: string = '∫' - } - - const fakeReadModelsMetadata: TargetTypesMap = { - ARegularReadModel: { - class: ARegularReadModel, - properties: [], - }, - } - - const typeInformer = new GraphQLTypeInformer({ - ...fakeReadModelsMetadata, - }) - - const config = new BoosterConfig('test') - - const graphQLQueryGenerator = new GraphQLQueryGenerator( - config, - fakeReadModelsMetadata, - typeInformer, - () => fake(), - () => fake(), - fake() - ) as any // So we can see private methods - - it('generates a query named after the read model class that accepts a unique ID', () => { - const fakeByIdResolverBuilder = fake.returns(fake()) - replace(graphQLQueryGenerator, 'byIDResolverBuilder', fakeByIdResolverBuilder) - - const query = graphQLQueryGenerator.generateByIdQuery('ARegularReadModel') - - expect(query.type).to.has.a.property('name', 'ARegularReadModel') - expect(query.args).to.have.a.property('id') - expect(query.resolve).to.be.a('Function') - expect(fakeByIdResolverBuilder).to.have.been.calledWith(ARegularReadModel) - }) - }) - - describe('the `generateByIdAndSequenceKeyQuery` private method', () => { - class AnotherSequencedReadModel { - readonly id: string = 'µ' - readonly timestamp: string = '™' - } - - const fakeReadModelsMetadata: TargetTypesMap = { - AnotherSequencedReadModel: { - class: AnotherSequencedReadModel, - properties: [], - }, - } - - const typeInformer = new GraphQLTypeInformer({ - ...fakeReadModelsMetadata, - }) - - const config = new BoosterConfig('test') - - const graphQLQueryGenerator = new GraphQLQueryGenerator( - config, - fakeReadModelsMetadata, - typeInformer, - () => fake(), - () => fake(), - fake() - ) as any // So we can see private methods - it('generates a query named after the read model class that accepts an ID and a sequence key', () => { - const fakeByIdResolverBuilder = fake.returns(fake()) - replace(graphQLQueryGenerator, 'byIDResolverBuilder', fakeByIdResolverBuilder) - - const query = graphQLQueryGenerator.generateByIdAndSequenceKeyQuery('AnotherSequencedReadModel', 'timestamp') - - expect(query.type).to.be.a('GraphQLList') - expect(query.type.ofType).to.have.a.property('name', 'AnotherSequencedReadModel') - expect(query.args).to.have.a.property('id') - expect(query.args).to.have.a.property('timestamp') - expect(query.resolve).to.be.a('Function') - expect(fakeByIdResolverBuilder).to.have.been.calledWith(AnotherSequencedReadModel) - }) - }) - describe('the `generateFilterQueries` private method', () => { it( 'generates a query named after the plural version of the read model name that responds with a list of read model instances' diff --git a/packages/framework-core/test/services/graphql/query-generators/graphql-query-by-keys-generator.test.ts b/packages/framework-core/test/services/graphql/query-generators/graphql-query-by-keys-generator.test.ts new file mode 100644 index 000000000..1abc5f004 --- /dev/null +++ b/packages/framework-core/test/services/graphql/query-generators/graphql-query-by-keys-generator.test.ts @@ -0,0 +1,132 @@ +/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/ban-ts-comment */ +import { replace, restore, fake } from 'sinon' +import { expect } from '../../../expect' +import { GraphQLTypeInformer } from '../../../../src/services/graphql/graphql-type-informer' +import { TargetTypesMap } from '../../../../src/services/graphql/common' +import { BoosterConfig, UUID, TimeKey } from '@boostercloud/framework-types' +import { GraphqlQueryByKeysGenerator } from '../../../../src/services/graphql/query-generators/graphql-query-by-keys-generator' + +describe('GraphQLQueryGenerator', () => { + afterEach(() => { + restore() + }) + + describe('the `generateByKeysQueries` private method', () => { + class AnotherReadModel { + public constructor(readonly id: UUID, readonly otherField: string) {} + } + + class ASequencedReadModel { + public constructor(readonly id: UUID, readonly timestamp: TimeKey) {} + } + + const fakeReadModelsMetadata: TargetTypesMap = { + AnotherReadModel: { + class: AnotherReadModel, + properties: [], + }, + ASequencedReadModel: { + class: ASequencedReadModel, + properties: [], + }, + } + + const typeInformer = new GraphQLTypeInformer({ + ...fakeReadModelsMetadata, + }) + + const config = new BoosterConfig('test') + config.readModelSequenceKeys['ASequencedReadModel'] = 'timestamp' + + const graphqlQueryByKeysGenerator = new GraphqlQueryByKeysGenerator( + config, + fakeReadModelsMetadata, + typeInformer, + () => fake() + ) as any // So we can see private methods + + it('generates by ID and sequenced queries', () => { + const fakeGenerateByIdQuery = fake() + replace(graphqlQueryByKeysGenerator, 'generateByIdQuery', fakeGenerateByIdQuery) + const fakeGenerateByIdAndSequenceKeyQuery = fake() + replace(graphqlQueryByKeysGenerator, 'generateByIdAndSequenceKeyQuery', fakeGenerateByIdAndSequenceKeyQuery) + + graphqlQueryByKeysGenerator.generateByKeysQueries() + + expect(fakeGenerateByIdQuery).to.have.been.calledOnceWith('AnotherReadModel') + expect(fakeGenerateByIdAndSequenceKeyQuery).to.have.been.calledOnceWith('ASequencedReadModel', 'timestamp') + }) + }) + + describe('the `generateByIdQuery` private method', () => { + class ARegularReadModel { + readonly id: string = '∫' + } + + const fakeReadModelsMetadata: TargetTypesMap = { + ARegularReadModel: { + class: ARegularReadModel, + properties: [], + }, + } + + const typeInformer = new GraphQLTypeInformer({ + ...fakeReadModelsMetadata, + }) + + const config = new BoosterConfig('test') + + const graphQLQueryGenerator = new GraphqlQueryByKeysGenerator(config, fakeReadModelsMetadata, typeInformer, () => + fake() + ) as any // So we can see private methods + + it('generates a query named after the read model class that accepts a unique ID', () => { + const fakeByIdResolverBuilder = fake.returns(fake()) + replace(graphQLQueryGenerator, 'byIDResolverBuilder', fakeByIdResolverBuilder) + + const query = graphQLQueryGenerator.generateByIdQuery('ARegularReadModel') + + expect(query.type).to.has.a.property('name', 'ARegularReadModel') + expect(query.args).to.have.a.property('id') + expect(query.resolve).to.be.a('Function') + expect(fakeByIdResolverBuilder).to.have.been.calledWith(ARegularReadModel) + }) + }) + + describe('the `generateByIdAndSequenceKeyQuery` private method', () => { + class AnotherSequencedReadModel { + readonly id: string = 'µ' + readonly timestamp: string = '™' + } + + const fakeReadModelsMetadata: TargetTypesMap = { + AnotherSequencedReadModel: { + class: AnotherSequencedReadModel, + properties: [], + }, + } + + const typeInformer = new GraphQLTypeInformer({ + ...fakeReadModelsMetadata, + }) + + const config = new BoosterConfig('test') + + const graphQLQueryGenerator = new GraphqlQueryByKeysGenerator(config, fakeReadModelsMetadata, typeInformer, () => + fake() + ) as any // So we can see private methods + it('generates a query named after the read model class that accepts an ID and a sequence key', () => { + const fakeByIdResolverBuilder = fake.returns(fake()) + replace(graphQLQueryGenerator, 'byIDResolverBuilder', fakeByIdResolverBuilder) + + const query = graphQLQueryGenerator.generateByIdAndSequenceKeyQuery('AnotherSequencedReadModel', 'timestamp') + + expect(query.type).to.be.a('GraphQLList') + expect(query.type.ofType).to.have.a.property('name', 'AnotherSequencedReadModel') + expect(query.args).to.have.a.property('id') + expect(query.args).to.have.a.property('timestamp') + expect(query.resolve).to.be.a('Function') + expect(fakeByIdResolverBuilder).to.have.been.calledWith(AnotherSequencedReadModel) + }) + }) +}) diff --git a/packages/framework-integration-tests/integration/provider-unaware/end-to-end/read-models.integration.ts b/packages/framework-integration-tests/integration/provider-unaware/end-to-end/read-models.integration.ts index e1adb9ab5..207c9d580 100644 --- a/packages/framework-integration-tests/integration/provider-unaware/end-to-end/read-models.integration.ts +++ b/packages/framework-integration-tests/integration/provider-unaware/end-to-end/read-models.integration.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { ApolloClient } from 'apollo-client' import { NormalizedCacheObject } from 'apollo-cache-inmemory' import { random } from 'faker' @@ -308,7 +309,7 @@ describe('Read models end-to-end tests', () => { ) }) - it('should retrieve a list of carts', async () => { + it('should retrieve a list of carts using deprecated methods', async () => { const queryResult = await waitForIt( () => { return client.query({ @@ -330,7 +331,7 @@ describe('Read models end-to-end tests', () => { expect(cartData.length).to.be.gte(1) }) - it('should retrieve a specific cart using filters', async () => { + it('should retrieve a specific cart using filters using deprecated methods', async () => { const filter = { id: { eq: mockCartId } } const queryResult = await waitForIt( () => { @@ -384,7 +385,7 @@ describe('Read models end-to-end tests', () => { expect(cartData[0].id).to.equal(mockCartId) }) - it('should retrieve a list of carts using complex filters', async () => { + it('should retrieve a list of carts using complex filters and deprecated methods', async () => { const filter = { cartItems: { includes: { productId: mockProductId, quantity: 2 } } } const queryResult = await waitForIt( () => { @@ -492,6 +493,246 @@ describe('Read models end-to-end tests', () => { expect(cartData.length).to.equal(1) expect(cartData[0].id).to.equal(mockCartId) }) + + it('should not fail if search a list of carts with empty results', async () => { + const filter = { cartItems: { includes: { productId: mockProductId, quantity: 200 } } } + const queryResult = await client.query({ + variables: { + filter: filter, + }, + query: gql` + query ListCartReadModels($filter: ListCartReadModelFilter) { + ListCartReadModels(filter: $filter) { + items { + id + } + } + } + `, + }) + + const cartData = queryResult?.data?.ListCartReadModels.items + + expect(cartData.length).to.equal(0) + }) + }) + + context('query sorted lists of carts', () => { + const mockCartItems: Array<{ id: string; productId: string; quantity: number; firstName: string }> = [] + const cartItems = 5 + + beforeEach(async () => { + for (let i = 0; i < cartItems; i++) { + const mockQuantity: number = i + const mockProductId: string = random.uuid() + const mockCartId: string = random.uuid() + const mockFirstName = String.fromCharCode(i + 65) + mockCartItems.push({ + id: mockCartId, + productId: mockProductId, + quantity: mockQuantity, + firstName: mockFirstName, + }) + + await client.mutate({ + variables: { + cartId: mockCartId, + productId: mockProductId, + quantity: mockQuantity, + }, + mutation: gql` + mutation ChangeCartItem($cartId: ID!, $productId: ID!, $quantity: Float) { + ChangeCartItem(input: { cartId: $cartId, productId: $productId, quantity: $quantity }) + } + `, + }) + + await waitForIt( + () => { + return client.query({ + variables: { + cartId: mockCartId, + }, + query: gql` + query CartReadModel($cartId: ID!) { + CartReadModel(id: $cartId) { + id + cartItems + } + } + `, + }) + }, + (result) => result?.data?.CartReadModel != null + ) + + await client.mutate({ + variables: { + cartId: mockCartId, + firstName: mockFirstName, + }, + mutation: gql` + mutation UpdateShippingAddress($cartId: ID, $firstName: String) { + UpdateShippingAddress(input: { cartId: $cartId, address: { firstName: $firstName } }) + } + `, + }) + + await waitForIt( + () => { + return client.query({ + variables: { + cartId: mockCartId, + }, + query: gql` + query CartReadModel($cartId: ID!) { + CartReadModel(id: $cartId) { + id + cartItems + shippingAddress { + firstName + } + } + } + `, + }) + }, + (result) => result?.data?.CartReadModel?.shippingAddress?.firstName != null + ) + } + }) + + afterEach(async () => { + mockCartItems.length = 0 + }) + + it('should retrieve a sorted list of carts using paginated read model', async () => { + if (process.env.TESTED_PROVIDER !== 'AZURE' && process.env.TESTED_PROVIDER !== 'LOCAL') { + console.log('****************** Warning **********************') + console.log('Only Azure and Local provider implement the sort option') + console.log('*************************************************') + return + } + + const expectedIds = mockCartItems.map((item) => item.id) + const mockedSortBy = { id: 'DESC' } + const queryResult = await waitForIt( + () => { + return client.query({ + variables: { + filterBy: { id: { in: expectedIds } }, + sortBy: mockedSortBy, + }, + query: gql` + query ListCartReadModels($filterBy: ListCartReadModelFilter, $sortBy: CartReadModelSortBy) { + ListCartReadModels(filter: $filterBy, sortBy: $sortBy) { + items { + id + } + } + } + `, + }) + }, + (result) => result?.data?.ListCartReadModels?.items.length === cartItems + ) + + const cartData = queryResult.data.ListCartReadModels.items + + expect(cartData).to.be.an('array') + const reverseExpectedIds = expectedIds.sort((a, b) => { + return a > b ? -1 : a < b ? 1 : 0 + }) + // @ts-ignore + expect(cartData.map((item: unknown) => item.id)).to.be.eql(reverseExpectedIds) + }) + + it('should retrieve a sorted list of carts using nested fields', async () => { + if (process.env.TESTED_PROVIDER !== 'AZURE' && process.env.TESTED_PROVIDER !== 'LOCAL') { + console.log('****************** Warning **********************') + console.log('Only Azure and Local provider implement the sort option') + console.log('*************************************************') + return + } + + const expectedIds = mockCartItems.map((item) => item.id) + const mockedSortBy = { shippingAddress: { firstName: 'DESC' } } + const queryResult = await waitForIt( + () => { + return client.query({ + variables: { + filterBy: { id: { in: expectedIds } }, + sortBy: mockedSortBy, + }, + query: gql` + query ListCartReadModels($filterBy: ListCartReadModelFilter, $sortBy: CartReadModelSortBy) { + ListCartReadModels(filter: $filterBy, sortBy: $sortBy) { + items { + id + shippingAddress { + firstName + } + } + } + } + `, + }) + }, + (result) => result?.data?.ListCartReadModels?.items.length === cartItems + ) + + const cartData = queryResult.data.ListCartReadModels.items + + expect(cartData).to.be.an('array') + const expectedFirstNames = mockCartItems.map((item) => item.firstName) + const reverseExpectedFirstNames = expectedFirstNames.sort((a, b) => { + return a > b ? -1 : a < b ? 1 : 0 + }) + // @ts-ignore + const names = cartData.map((item: unknown) => item.shippingAddress.firstName) + expect(names).to.be.eql(reverseExpectedFirstNames) + }) + + it('should retrieve a sorted list of carts using two fields', async () => { + if (process.env.TESTED_PROVIDER !== 'LOCAL') { + console.log('****************** Warning **********************') + console.log('Only Local provider implement the sort option for more than one sort field') + console.log('*************************************************') + return + } + + const expectedIds = mockCartItems.map((item) => item.id) + const mockedSortBy = { id: 'DESC', shippingAddress: { firstName: 'ASC' } } + const queryResult = await waitForIt( + () => { + return client.query({ + variables: { + filterBy: { id: { in: expectedIds } }, + sortBy: mockedSortBy, + }, + query: gql` + query ListCartReadModels($filterBy: ListCartReadModelFilter, $sortBy: CartReadModelSortBy) { + ListCartReadModels(filter: $filterBy, sortBy: $sortBy) { + items { + id + } + } + } + `, + }) + }, + (result) => result?.data?.ListCartReadModels?.items.length === cartItems + ) + + const cartData = queryResult.data.ListCartReadModels.items + + expect(cartData).to.be.an('array') + const reverseExpectedIds = expectedIds.sort((a, b) => { + return a > b ? -1 : a < b ? 1 : 0 + }) + // @ts-ignore + expect(cartData.map((item: unknown) => item.id)).to.be.eql(reverseExpectedIds) + }) }) context('query using pagination', () => { diff --git a/packages/framework-provider-aws/src/library/read-models-searcher-adapter.ts b/packages/framework-provider-aws/src/library/read-models-searcher-adapter.ts index 1969c5c08..841489fab 100644 --- a/packages/framework-provider-aws/src/library/read-models-searcher-adapter.ts +++ b/packages/framework-provider-aws/src/library/read-models-searcher-adapter.ts @@ -5,6 +5,7 @@ import { InvalidParameterError, Logger, Operation, + SortFor, ReadModelListResult, } from '@boostercloud/framework-types' import { DynamoDB } from 'aws-sdk' @@ -18,10 +19,14 @@ export async function searchReadModel( logger: Logger, readModelName: string, filters: FilterFor, + sortBy?: SortFor, limit?: number, afterCursor?: DynamoDB.DocumentClient.Key | undefined, paginatedVersion = false ): Promise | ReadModelListResult> { + if (sortBy) { + logger.info('SortBy not implemented for AWS provider. It will be ignored') + } let params: DocumentClient.ScanInput = { TableName: config.resourceNames.forReadModel(readModelName), ConsistentRead: true, diff --git a/packages/framework-provider-azure/src/helpers/query-helper.ts b/packages/framework-provider-azure/src/helpers/query-helper.ts index 237bd0818..61f24177e 100644 --- a/packages/framework-provider-azure/src/helpers/query-helper.ts +++ b/packages/framework-provider-azure/src/helpers/query-helper.ts @@ -6,6 +6,7 @@ import { Logger, Operation, ReadModelListResult, + SortFor, } from '@boostercloud/framework-types' export async function search( @@ -14,10 +15,10 @@ export async function search( logger: Logger, containerName: string, filters: FilterFor, - limit?: number, + limit?: number | undefined, afterCursor?: Record | undefined, paginatedVersion = false, - order?: Record + order?: SortFor ): Promise | ReadModelListResult> { const filterExpression = buildFilterExpression(filters) const queryDefinition = `SELECT * FROM c ${filterExpression !== '' ? `WHERE ${filterExpression}` : filterExpression}` @@ -182,11 +183,26 @@ function buildAttributeValue( return attributeValues } -function buildOrderExpression(order: Record | undefined): string { - if (!order || !Object.keys(order).length) return '' - let orderQuery = ' ORDER BY' - orderQuery += Object.entries(order) - .map(([key, value]) => ` c.${key} ${value}`) - .join(',') +function buildOrderExpression(sortFor: SortFor | undefined): string { + if (!sortFor || !Object.keys(sortFor).length) return '' + let orderQuery = ' ORDER BY ' + orderQuery += toLocalSortFor(sortFor)?.join(', ') return orderQuery } + +function toLocalSortFor( + sortBy?: SortFor, + parentKey = '', + sortedList: Array = [] +): undefined | Array { + if (!sortBy || Object.keys(sortBy).length === 0) return + const elements = sortBy! + Object.entries(elements).forEach(([key, value]) => { + if (typeof value === 'string') { + sortedList.push(`c.${parentKey}${key} ${value}`) + } else { + toLocalSortFor(value as SortFor, `${parentKey}${key}.`, sortedList) + } + }) + return sortedList +} diff --git a/packages/framework-provider-azure/src/library/searcher-adapter.ts b/packages/framework-provider-azure/src/library/searcher-adapter.ts index 15cf4ae1b..f22c6dad5 100644 --- a/packages/framework-provider-azure/src/library/searcher-adapter.ts +++ b/packages/framework-provider-azure/src/library/searcher-adapter.ts @@ -1,6 +1,6 @@ import { CosmosClient } from '@azure/cosmos' -import { BoosterConfig, Logger, FilterFor, ReadModelListResult } from '@boostercloud/framework-types' -import { search } from '../helpers/query-helper' +import { BoosterConfig, FilterFor, Logger, ReadModelListResult, SortFor } from '@boostercloud/framework-types' +import * as queryHelper from '../helpers/query-helper' export async function searchReadModel( cosmosDb: CosmosClient, @@ -8,11 +8,12 @@ export async function searchReadModel( logger: Logger, readModelName: string, filters: FilterFor, + sortBy?: SortFor, limit?: number, afterCursor?: Record | undefined, paginatedVersion = false ): Promise | ReadModelListResult> { - return await search( + return await queryHelper.search( cosmosDb, config, logger, @@ -20,6 +21,7 @@ export async function searchReadModel( filters, limit, afterCursor, - paginatedVersion + paginatedVersion, + sortBy ) } diff --git a/packages/framework-provider-azure/test/helpers/query-helper.test.ts b/packages/framework-provider-azure/test/helpers/query-helper.test.ts index 1c4cf9512..cb18b36e3 100644 --- a/packages/framework-provider-azure/test/helpers/query-helper.test.ts +++ b/packages/framework-provider-azure/test/helpers/query-helper.test.ts @@ -320,6 +320,32 @@ describe('Query helper', () => { ) }) + it('Supports order for nested fields', async () => { + const filters: FilterFor = {} + const order = { sku: 'DESC', address: { street: 'ASC' } } + await search( + mockCosmosDbClient as any, + mockConfig, + mockLogger, + mockReadModelName, + filters, + undefined, + undefined, + undefined, + order + ) + + expect( + mockCosmosDbClient.database(mockConfig.resourceNames.applicationStack).container(`${mockReadModelName}`).items + .query + ).to.have.been.calledWith( + match({ + query: 'SELECT * FROM c ORDER BY c.sku DESC, c.address.street ASC', + parameters: [], + }) + ) + }) + it('Supports limited results', async () => { const filters: FilterFor = { days: { includes: 2 }, diff --git a/packages/framework-provider-local/src/library/read-model-adapter.ts b/packages/framework-provider-local/src/library/read-model-adapter.ts index 897f00e5f..6b5ff322a 100644 --- a/packages/framework-provider-local/src/library/read-model-adapter.ts +++ b/packages/framework-provider-local/src/library/read-model-adapter.ts @@ -7,9 +7,10 @@ import { ReadModelInterface, ReadModelListResult, ReadOnlyNonEmptyArray, + SortFor, UUID, } from '@boostercloud/framework-types' -import { ReadModelRegistry } from '../services/read-model-registry' +import { ReadModelRegistry } from '../services' import { queryRecordFor } from './searcher-adapter' export async function rawReadModelEventsToEnvelopes( @@ -68,7 +69,8 @@ export async function searchReadModel( _config: BoosterConfig, logger: Logger, readModelName: string, - filters: FilterFor, + filters: FilterFor, + sortBy?: SortFor, limit?: number, afterCursor?: Record | undefined, paginatedVersion = false @@ -78,9 +80,8 @@ export async function searchReadModel( const query = queryRecordFor(readModelName, filters) logger.debug('Got query ', query) const skipId = afterCursor?.id ? parseInt(afterCursor?.id) : 0 - const result = await db.query(query, skipId, limit) + const result = await db.query(query, sortBy, skipId, limit) logger.debug('[ReadModelAdapter#searchReadModel] Search result: ', result) - const items = result?.map((envelope) => envelope.value) ?? [] if (paginatedVersion) { return { diff --git a/packages/framework-provider-local/src/services/read-model-registry.ts b/packages/framework-provider-local/src/services/read-model-registry.ts index 79ad229ce..c2c29d3eb 100644 --- a/packages/framework-provider-local/src/services/read-model-registry.ts +++ b/packages/framework-provider-local/src/services/read-model-registry.ts @@ -1,15 +1,28 @@ -import { ReadModelEnvelope, UUID } from '@boostercloud/framework-types' +import { ReadModelEnvelope, SortFor, UUID } from '@boostercloud/framework-types' import * as DataStore from 'nedb' import { readModelsDatabase } from '../paths' +interface LocalSortedFor { + [key: string]: number +} + export class ReadModelRegistry { public readonly readModels: DataStore = new DataStore(readModelsDatabase) constructor() { this.readModels.loadDatabase() } - public async query(query: object, skip?: number, limit?: number): Promise> { + public async query( + query: object, + sortBy?: SortFor, + skip?: number, + limit?: number + ): Promise> { let cursor = this.readModels.find(query) + const sortByList = this.toLocalSortFor(sortBy) + if (sortByList) { + cursor = cursor.sort(sortByList) + } if (skip) { cursor = cursor.skip(skip) } @@ -50,4 +63,21 @@ export class ReadModelRegistry { return deletePromise as Promise } + + toLocalSortFor( + sortBy?: SortFor, + parentKey = '', + sortedList: LocalSortedFor = {} + ): undefined | LocalSortedFor { + if (!sortBy || Object.keys(sortBy).length === 0) return + const elements = sortBy! + Object.entries(elements).forEach(([key, value]) => { + if (typeof value === 'string') { + sortedList[`value.${parentKey}${key}`] = (value as string) === 'ASC' ? 1 : -1 + } else { + this.toLocalSortFor(value as SortFor, `${parentKey}${key}.`, sortedList) + } + }) + return sortedList + } } diff --git a/packages/framework-provider-local/test/helpers/read-model-helper.ts b/packages/framework-provider-local/test/helpers/read-model-helper.ts index 03a079604..b0fa2140e 100644 --- a/packages/framework-provider-local/test/helpers/read-model-helper.ts +++ b/packages/framework-provider-local/test/helpers/read-model-helper.ts @@ -1,5 +1,6 @@ import { ReadModelEnvelope } from '@boostercloud/framework-types' import { random } from 'faker' +import { expect } from '../expect' export function createMockReadModelEnvelope(): ReadModelEnvelope { return { @@ -12,3 +13,24 @@ export function createMockReadModelEnvelope(): ReadModelEnvelope { typeName: random.word(), } } + +export function assertOrderByAgeDesc(result: Array): void { + const readModelEnvelopes = [...result] as Array + const expectedResult = readModelEnvelopes.sort(function (a: ReadModelEnvelope, b: ReadModelEnvelope) { + return a.value.age > b.value.age ? -1 : 1 + }) + + expect(result).to.eql(expectedResult) +} + +export function assertOrderByAgeAndIdDesc(result: Array): void { + const readModelEnvelopes = [...result] as Array + const expectedResult = readModelEnvelopes.sort(function (a: ReadModelEnvelope, b: ReadModelEnvelope) { + if (a.value.age === b.value.age) { + return a.value.id > b.value.id ? -1 : 1 + } + return a.value.age > b.value.age ? -1 : 1 + }) + + expect(result).to.eql(expectedResult) +} diff --git a/packages/framework-provider-local/test/library/read-model-adapter.test.ts b/packages/framework-provider-local/test/library/read-model-adapter.test.ts index c5e37d7e0..2a503a404 100644 --- a/packages/framework-provider-local/test/library/read-model-adapter.test.ts +++ b/packages/framework-provider-local/test/library/read-model-adapter.test.ts @@ -1,6 +1,16 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { createStubInstance, fake, SinonStub, SinonStubbedInstance, replace, stub } from 'sinon' -import { ReadModelRegistry } from '../../src/services' -import { BoosterConfig, Logger, ReadModelEnvelope, ReadModelInterface, UUID } from '@boostercloud/framework-types' +import { ReadModelRegistry } from '../../src' +import { + BoosterConfig, + FilterFor, + Logger, + ReadModelEnvelope, + ReadModelInterface, + ReadOnlyNonEmptyArray, + SortFor, + UUID, +} from '@boostercloud/framework-types' import { expect } from '../expect' import { random } from 'faker' @@ -12,6 +22,50 @@ import { storeReadModel, } from '../../src/library/read-model-adapter' +async function fetchMock( + mockReadModelRegistry: SinonStubbedInstance, + mockConfig: BoosterConfig, + mockLogger: Logger, + mockReadModelTypeName: string, + mockReadModelID: UUID +): Promise> { + // @ts-ignore + return await fetchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModelTypeName, mockReadModelID) +} + +async function storeMock( + mockReadModelRegistry: SinonStubbedInstance, + mockConfig: BoosterConfig, + mockLogger: Logger, + mockReadModel: ReadModelEnvelope +): Promise { + // @ts-ignore + await storeReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, mockReadModel.value, 1) +} + +async function searchMock( + mockReadModelRegistry: SinonStubbedInstance, + mockConfig: BoosterConfig, + mockLogger: Logger, + mockReadModel: ReadModelEnvelope, + filters: FilterFor, + sortBy?: SortFor, + limit?: number, + afterCursor?: Record | undefined +): Promise { + // @ts-ignore + await searchReadModel( + mockReadModelRegistry as any, + mockConfig, + mockLogger, + mockReadModel.typeName, + filters, + sortBy, + limit, + afterCursor + ) +} + describe('read-models-adapter', () => { let mockConfig: BoosterConfig let mockLogger: Logger @@ -21,6 +75,7 @@ describe('read-models-adapter', () => { let storeStub: SinonStub let queryStub: SinonStub + type StubbedClass = SinonStubbedInstance & T let mockReadModelRegistry: SinonStubbedInstance beforeEach(() => { @@ -37,7 +92,7 @@ describe('read-models-adapter', () => { error: fake(), debug: loggerDebugStub, } - mockReadModelRegistry = createStubInstance(ReadModelRegistry) + mockReadModelRegistry = createStubInstance(ReadModelRegistry) as StubbedClass mockReadModel = createMockReadModelEnvelope() // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -75,7 +130,7 @@ describe('read-models-adapter', () => { it('should call read model registry query and return a value', async () => { queryStub.resolves([mockReadModel]) const result: ReadModelInterface = ( - await fetchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModelTypeName, mockReadModelID) + await fetchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModelTypeName, mockReadModelID) )[0] expect(queryStub).to.have.been.calledOnceWithExactly({ @@ -94,7 +149,7 @@ describe('read-models-adapter', () => { it('should call read model registry query and no results', async () => { queryStub.resolves([]) const result = ( - await fetchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModelTypeName, mockReadModelID) + await fetchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModelTypeName, mockReadModelID) )[0] expect(queryStub).to.have.been.calledOnceWithExactly({ @@ -117,14 +172,7 @@ describe('read-models-adapter', () => { beforeEach(async () => { mockReadModel = createMockReadModelEnvelope() - await storeReadModel( - mockReadModelRegistry, - mockConfig, - mockLogger, - mockReadModel.typeName, - mockReadModel.value, - 1 - ) + await storeMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel) }) it('should call read model registry store', () => { @@ -139,18 +187,26 @@ describe('read-models-adapter', () => { describe('searchReadModel', () => { it('empty query should call read model registry store', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, {}) - expect(queryStub).to.have.been.calledWithExactly({ typeName: mockReadModel.typeName }, 0, undefined) + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, {}) + expect(queryStub).to.have.been.calledWithExactly( + { + typeName: mockReadModel.typeName, + }, + undefined, + 0, + undefined + ) }) describe('query by one field', () => { it('eq query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { foo: { eq: 1 }, }) expect(queryStub).to.have.been.calledWithExactly( { typeName: mockReadModel.typeName, 'value.foo': 1 }, + undefined, 0, undefined ) @@ -158,11 +214,12 @@ describe('read-models-adapter', () => { it('ne query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { foo: { ne: 1 }, }) expect(queryStub).to.have.been.calledWithExactly( { typeName: mockReadModel.typeName, 'value.foo': { $ne: 1 } }, + undefined, 0, undefined ) @@ -170,11 +227,12 @@ describe('read-models-adapter', () => { it('lt query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { foo: { lt: 1 }, }) expect(queryStub).to.have.been.calledWithExactly( { typeName: mockReadModel.typeName, 'value.foo': { $lt: 1 } }, + undefined, 0, undefined ) @@ -182,11 +240,13 @@ describe('read-models-adapter', () => { it('gt query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { foo: { gt: 1 }, }) + expect(queryStub).to.have.been.calledWithExactly( { typeName: mockReadModel.typeName, 'value.foo': { $gt: 1 } }, + undefined, 0, undefined ) @@ -194,11 +254,12 @@ describe('read-models-adapter', () => { it('lte query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { foo: { lte: 1 }, }) expect(queryStub).to.have.been.calledWithExactly( { typeName: mockReadModel.typeName, 'value.foo': { $lte: 1 } }, + undefined, 0, undefined ) @@ -206,11 +267,12 @@ describe('read-models-adapter', () => { it('gte query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { foo: { gte: 1 }, }) expect(queryStub).to.have.been.calledWithExactly( { typeName: mockReadModel.typeName, 'value.foo': { $gte: 1 } }, + undefined, 0, undefined ) @@ -218,7 +280,7 @@ describe('read-models-adapter', () => { it('gte query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { foo: { in: [1, 2, 3] }, }) expect(queryStub).to.have.been.calledWithExactly( @@ -226,6 +288,7 @@ describe('read-models-adapter', () => { typeName: mockReadModel.typeName, 'value.foo': { $in: [1, 2, 3] }, }, + undefined, 0, undefined ) @@ -233,7 +296,7 @@ describe('read-models-adapter', () => { it('contains query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { foo: { contains: 'bar' }, }) expect(queryStub).to.have.been.calledWithExactly( @@ -241,6 +304,7 @@ describe('read-models-adapter', () => { typeName: mockReadModel.typeName, 'value.foo': { $regex: new RegExp('bar') }, }, + undefined, 0, undefined ) @@ -248,7 +312,7 @@ describe('read-models-adapter', () => { it('includes query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { foo: { includes: 'bar' }, }) expect(queryStub).to.have.been.calledWithExactly( @@ -256,6 +320,7 @@ describe('read-models-adapter', () => { typeName: mockReadModel.typeName, 'value.foo': { $regex: new RegExp('bar') }, }, + undefined, 0, undefined ) @@ -263,7 +328,7 @@ describe('read-models-adapter', () => { it('includes object query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { foo: { includes: { bar: 'baz' } }, }) expect(queryStub).to.have.been.calledWithExactly( @@ -271,6 +336,7 @@ describe('read-models-adapter', () => { typeName: mockReadModel.typeName, 'value.foo': { $elemMatch: { bar: 'baz' } }, }, + undefined, 0, undefined ) @@ -278,7 +344,7 @@ describe('read-models-adapter', () => { it('beginsWith query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { foo: { beginsWith: 'bar' }, }) expect(queryStub).to.have.been.calledWithExactly( @@ -286,6 +352,7 @@ describe('read-models-adapter', () => { typeName: mockReadModel.typeName, 'value.foo': { $regex: new RegExp('^bar') }, }, + undefined, 0, undefined ) @@ -293,7 +360,7 @@ describe('read-models-adapter', () => { it('NOT beginsWith query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { not: { foo: { beginsWith: 'bar' } }, }) expect(queryStub).to.have.been.calledWithExactly( @@ -301,6 +368,7 @@ describe('read-models-adapter', () => { typeName: mockReadModel.typeName, $not: { 'value.foo': { $regex: new RegExp('^bar') }, typeName: mockReadModel.typeName }, }, + undefined, 0, undefined ) @@ -310,7 +378,7 @@ describe('read-models-adapter', () => { describe('multiple queries', () => { it('gt lt AND query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { and: [{ foo: { gt: 1 } }, { foo: { lt: 10 } }], }) expect(queryStub).to.have.been.calledWithExactly( @@ -321,6 +389,7 @@ describe('read-models-adapter', () => { { 'value.foo': { $lt: 10 }, typeName: mockReadModel.typeName }, ], }, + undefined, 0, undefined ) @@ -328,7 +397,7 @@ describe('read-models-adapter', () => { it('gte lte AND query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { and: [{ foo: { gte: 1 } }, { foo: { lte: 10 } }], }) expect(queryStub).to.have.been.calledWithExactly( @@ -339,6 +408,7 @@ describe('read-models-adapter', () => { { 'value.foo': { $lte: 10 }, typeName: mockReadModel.typeName }, ], }, + undefined, 0, undefined ) @@ -346,7 +416,7 @@ describe('read-models-adapter', () => { it('OR query should call read model registry store with the appropriate operation converted', async () => { const mockReadModel = createMockReadModelEnvelope() - await searchReadModel(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel.typeName, { + await searchMock(mockReadModelRegistry, mockConfig, mockLogger, mockReadModel, { or: [{ foo: { eq: 1 } }, { bar: { lt: 10 } }], }) expect(queryStub).to.have.been.calledWithExactly( @@ -357,10 +427,48 @@ describe('read-models-adapter', () => { { 'value.bar': { $lt: 10 }, typeName: mockReadModel.typeName }, ], }, + undefined, 0, undefined ) }) }) + + describe('Sort fields', () => { + it('query should call read model registry store with sort fields, limits and skip', async () => { + const mockReadModel = createMockReadModelEnvelope() + await searchMock( + mockReadModelRegistry, + mockConfig, + mockLogger, + mockReadModel, + {}, + [ + { + field: 'ID', + order: 'DESC', + }, + { + field: 'anotherField', + order: 'ASC', + }, + ], + 3, + { id: '5' } + ) + expect(queryStub).to.have.been.calledWithExactly( + { typeName: mockReadModel.typeName }, + [ + { field: 'ID', order: 'DESC' }, + { + field: 'anotherField', + order: 'ASC', + }, + ], + 5, + 3 + ) + }) + }) }) }) diff --git a/packages/framework-provider-local/test/services/read-model-registry.test.ts b/packages/framework-provider-local/test/services/read-model-registry.test.ts index 8c18b2704..179ffb518 100644 --- a/packages/framework-provider-local/test/services/read-model-registry.test.ts +++ b/packages/framework-provider-local/test/services/read-model-registry.test.ts @@ -4,7 +4,11 @@ import { expect } from '../expect' import * as faker from 'faker' import { stub, restore } from 'sinon' import { ReadModelRegistry } from '../../src/services' -import { createMockReadModelEnvelope } from '../helpers/read-model-helper' +import { + assertOrderByAgeAndIdDesc, + assertOrderByAgeDesc, + createMockReadModelEnvelope, +} from '../helpers/read-model-helper' import { random } from 'faker' describe('the read model registry', () => { @@ -93,6 +97,31 @@ describe('the read model registry', () => { expect(result.length).to.be.equal(initialReadModelsCount + 1) }) + it('should return all results sorted by Age', async () => { + const result = await readModelRegistry.query( + {}, + { + age: 'DESC', + } + ) + + expect(result.length).to.be.equal(initialReadModelsCount + 1) + assertOrderByAgeDesc(result) + }) + + it('should return all results sorted by Age and ID', async () => { + const result = await readModelRegistry.query( + {}, + { + age: 'DESC', + id: 'DESC', + } + ) + + expect(result.length).to.be.equal(initialReadModelsCount + 1) + assertOrderByAgeAndIdDesc(result) + }) + it('should return 1 result when age is less than or equal than max age', async () => { const result = await readModelRegistry.query({ 'value.age': { $lte: 40 }, @@ -178,7 +207,7 @@ describe('the read model registry', () => { readModelRegistry.readModels.update = stub().yields(error, null) - expect(readModelRegistry.store(readModel)).to.be.rejectedWith(error) + void expect(readModelRegistry.store(readModel)).to.be.rejectedWith(error) }) }) }) diff --git a/packages/framework-types/src/envelope.ts b/packages/framework-types/src/envelope.ts index 19b16bf0e..204a2250c 100644 --- a/packages/framework-types/src/envelope.ts +++ b/packages/framework-types/src/envelope.ts @@ -1,6 +1,6 @@ import { CommandInput, EntityInterface, EventInterface, ReadModelInterface, SequenceKey, UUID } from './concepts' import { GraphQLClientMessage } from './graphql-websocket-messages' -import { FilterFor } from './searcher' +import { FilterFor, SortFor } from './searcher' import { Class } from './typelevel' /** @@ -89,6 +89,7 @@ export interface ReadModelRequestEnvelope className: string version: number filters: ReadModelRequestProperties + sortBy?: ReadModelSortProperties limit?: number afterCursor?: unknown paginatedVersion?: boolean // Used only for retrocompatibility @@ -96,6 +97,7 @@ export interface ReadModelRequestEnvelope export interface ReadModelRequestArgs { filter?: ReadModelRequestProperties + sortBy?: ReadModelSortProperties limit?: number afterCursor?: unknown } @@ -107,6 +109,8 @@ export interface ReadModelByIdRequestArgs { export type ReadModelRequestProperties = Record> +export type ReadModelSortProperties = Record> + export interface GraphQLRequestEnvelope extends Envelope { eventType: 'CONNECT' | 'MESSAGE' | 'DISCONNECT' connectionID?: string diff --git a/packages/framework-types/src/provider.ts b/packages/framework-types/src/provider.ts index c63713fdb..a33b4e2bd 100644 --- a/packages/framework-types/src/provider.ts +++ b/packages/framework-types/src/provider.ts @@ -13,7 +13,7 @@ import { SubscriptionEnvelope, } from './envelope' import { Logger } from './logger' -import { FilterFor } from './searcher' +import { FilterFor, SortFor } from './searcher' import { ReadOnlyNonEmptyArray } from './typelevel' import { RocketDescriptor } from './rocket-descriptor' @@ -60,6 +60,7 @@ export interface ProviderReadModelsLibrary { logger: Logger, entityTypeName: string, filters: FilterFor, + sortBy?: SortFor, limit?: number, afterCursor?: unknown, paginatedVersion?: boolean diff --git a/packages/framework-types/src/searcher.ts b/packages/framework-types/src/searcher.ts index 5d0495e55..b3ab6aad7 100644 --- a/packages/framework-types/src/searcher.ts +++ b/packages/framework-types/src/searcher.ts @@ -6,6 +6,7 @@ import { Class, ReadOnlyNonEmptyArray } from './typelevel' export type SearcherFunction = ( className: string, filters: FilterFor, + sortBy: SortFor, limit?: number, afterCursor?: any, paginatedVersion?: boolean @@ -33,6 +34,7 @@ export class Searcher { private _limit?: number private _afterCursor?: any private filters: FilterFor = {} + private _sortByList: SortFor = {} private _paginatedVersion = false /** @@ -62,6 +64,11 @@ export class Searcher { return this } + public sortBy(sortBy?: SortFor): this { + if (sortBy) this._sortByList = sortBy + return this + } + public limit(limit?: number): this { if (limit) this._limit = limit return this @@ -86,6 +93,7 @@ export class Searcher { const searchResult = await this.searcherFunction( this.objectClass.name, this.filters, + this._sortByList, 1, // Forces limit 1 this._afterCursor, false // It doesn't make sense to paginate a single result, as pagination metadata would be discarded @@ -100,6 +108,7 @@ export class Searcher { return this.searcherFunction( this.objectClass.name, this.filters, + this._sortByList, this._limit, this._afterCursor, this._paginatedVersion @@ -107,6 +116,10 @@ export class Searcher { } } +export type SortFor = { + [TProp in keyof TType]?: SortFor | 'ASC' | 'DESC' +} + export type FilterFor = { [TProp in keyof TType]?: Operation } & diff --git a/packages/framework-types/test/searcher.test.ts b/packages/framework-types/test/searcher.test.ts index 0a732a24c..0971b5288 100644 --- a/packages/framework-types/test/searcher.test.ts +++ b/packages/framework-types/test/searcher.test.ts @@ -91,7 +91,7 @@ describe('the `Searcher` class', () => { const filters = { someField: { gt: '200' } } const result = await searcher.filter(filters).afterCursor('30').limit(50).paginatedVersion(true).searchOne() - expect(searcherFunction).to.have.been.calledWithMatch('SomeModel', filters, 1, '30', false) + expect(searcherFunction).to.have.been.calledWithMatch('SomeModel', filters, {}, 1, '30', false) expect(result).not.to.be.an('Array') }) }) @@ -101,7 +101,7 @@ describe('the `Searcher` class', () => { const filters = { someField: { gt: '200' } } await searcher.filter(filters).afterCursor('30').limit(50).paginatedVersion(true).search() - expect(searcherFunction).to.have.been.calledWithMatch('SomeModel', filters, 50, '30', true) + expect(searcherFunction).to.have.been.calledWithMatch('SomeModel', filters, {}, 50, '30', true) }) }) })