diff --git a/packages/@ama-sdk/client-angular/.eslintrc.js b/packages/@ama-sdk/client-angular/.eslintrc.js new file mode 100644 index 0000000000..f5ca5f3878 --- /dev/null +++ b/packages/@ama-sdk/client-angular/.eslintrc.js @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable quote-props */ + +module.exports = { + 'root': true, + 'parserOptions': { + 'tsconfigRootDir': __dirname, + 'project': [ + 'tsconfig.build.json', + 'tsconfig.builders.json', + 'tsconfig.spec.json', + 'tsconfig.eslint.json' + ], + 'sourceType': 'module' + }, + 'extends': [ + '../../../.eslintrc.js' + ] +}; diff --git a/packages/@ama-sdk/client-angular/.gitignore b/packages/@ama-sdk/client-angular/.gitignore new file mode 100644 index 0000000000..03eb2be86c --- /dev/null +++ b/packages/@ama-sdk/client-angular/.gitignore @@ -0,0 +1,10 @@ +/fwk +/helpers +/index.* +/bundles +/test +/dist-test +/plugins +/dist +/utils +/build diff --git a/packages/@ama-sdk/client-angular/.npmignore b/packages/@ama-sdk/client-angular/.npmignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/@ama-sdk/client-angular/README.md b/packages/@ama-sdk/client-angular/README.md new file mode 100644 index 0000000000..4be7258cc2 --- /dev/null +++ b/packages/@ama-sdk/client-angular/README.md @@ -0,0 +1,26 @@ +# Ama SDK Angular Client + +[![Stable Version](https://img.shields.io/npm/v/@ama-sdk/client-angular?style=for-the-badge)](https://www.npmjs.com/package/@ama-sdk/client-angular) +[![Bundle Size](https://img.shields.io/bundlephobia/min/@ama-sdk/client-angular?color=green&style=for-the-badge)](https://www.npmjs.com/package/@ama-sdk/client-angular) + +This package exposes the **Api Angular Client** from an SDK based on [@ama-sdk/core](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/core). + +This package contains all the [Angular Plugins](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/client-angular/src/plugins), helpers and object definitions to dialog with an API following the `ama-sdk` architecture. + +> [!TIP] +> Please refer to the [SDK initializer](https://www.npmjs.com/package/@ama-sdk/create) package for getting started with an API client SDK based on `ama-sdk` architecture. + +## Setup + +The **Api Angular Client** can be added to your project via the following command: + +```shell +ng add @ama-sdk/client-angular +``` + +> [!NOTE] +> In case of migration from deprecated `ApiAngularClient` imported from `@ama-sdk/core`, the `ng add` command will replace, in your existing code, the import from `@ama-sdk/core` to `@ama-sdk/client-angular` for the deprecated dependencies. + +## Available plugins + +- [mock-intercept](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/client-angular/src/plugins/mock-intercept) diff --git a/packages/@ama-sdk/client-angular/collection.json b/packages/@ama-sdk/client-angular/collection.json new file mode 100644 index 0000000000..2b3154fb52 --- /dev/null +++ b/packages/@ama-sdk/client-angular/collection.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/angular/angular-cli/main/packages/angular_devkit/schematics/collection-schema.json", + "schematics": { + "ng-add": { + "description": "Add the SDK Angular Client API to the project.", + "factory": "./schematics/ng-add/index#ngAdd", + "schema": "./schematics/ng-add/schema.json", + "aliases": ["install", "i"] + } + } +} diff --git a/packages/@ama-sdk/client-angular/jest.config.js b/packages/@ama-sdk/client-angular/jest.config.js new file mode 100644 index 0000000000..237fd70fc2 --- /dev/null +++ b/packages/@ama-sdk/client-angular/jest.config.js @@ -0,0 +1,10 @@ +const getJestGlobalConfig = require('../../../jest.config.ut').getJestGlobalConfig; + +/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ +module.exports = { + ...getJestGlobalConfig(), + projects: [ + '/testing/jest.config.ut.js', + '/testing/jest.config.ut.builders.js' + ] +}; diff --git a/packages/@ama-sdk/client-angular/package.json b/packages/@ama-sdk/client-angular/package.json new file mode 100644 index 0000000000..d0aa3c1249 --- /dev/null +++ b/packages/@ama-sdk/client-angular/package.json @@ -0,0 +1,123 @@ +{ + "name": "@ama-sdk/client-angular", + "version": "0.0.0-placeholder", + "publishConfig": { + "access": "public" + }, + "description": "Angular API Request client for @ama-sdk/core based SDK", + "module": "dist/src/public_api.js", + "esm2015": "dist/esm2015/public_api.js", + "esm2020": "dist/src/public_api.js", + "typings": "dist/src/public_api.d.ts", + "sideEffects": false, + "exports": { + "./package.json": { + "default": "./package.json" + }, + ".": { + "module": "./dist/src/public_api.js", + "esm2020": "./dist/src/public_api.js", + "esm2015": "./dist/esm2015/public_api.js", + "es2020": "./dist/cjs/public_api.js", + "default": "./dist/cjs/public_api.js", + "typings": "./dist/src/public_api.d.ts", + "import": "./dist/src/public_api.js", + "node": "./dist/cjs/public_api.js", + "require": "./dist/cjs/public_api.js" + } + }, + "scripts": { + "nx": "nx", + "ng": "yarn nx", + "build": "yarn nx build ama-sdk-client-angular", + "build:cjs": "swc src -d dist/cjs -C module.type=commonjs -q --strip-leading-paths", + "build:esm2015": "swc src -d dist/esm2015 -C module.type=es6 -q --strip-leading-paths", + "build:esm2020": "tsc -b tsconfig.build.json", + "postbuild": "yarn cpy './package.json' dist && patch-package-json-main", + "prepare:build:builders": "yarn cpy 'schematics/**/*.json' dist/schematics && yarn cpy 'collection.json' dist", + "build:builders": "tsc -b tsconfig.builders.json --pretty && yarn generate-cjs-manifest", + "prepare:publish": "prepare-publish ./dist" + }, + "dependencies": { + "@swc/helpers": "~0.5.0", + "tslib": "^2.6.2", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "@ama-sdk/core": "workspace:^", + "@angular-devkit/schematics": "~18.2.0", + "@angular/cli": "~18.2.0", + "@angular/common": "~18.2.0", + "@o3r/schematics": "workspace:^", + "@schematics/angular": "~18.2.0", + "rxjs": "^7.8.1", + "typescript": "~5.5.4" + }, + "peerDependenciesMeta": { + "@angular-devkit/schematics": { + "optional": true + }, + "@angular/cli": { + "optional": true + }, + "@angular/common": { + "optional": true + }, + "@o3r/schematics": { + "optional": true + }, + "@schematics/angular": { + "optional": true + }, + "typescript": { + "optional": true + } + }, + "devDependencies": { + "@ama-sdk/core": "workspace:^", + "@angular-devkit/core": "~18.2.0", + "@angular-devkit/schematics": "~18.2.0", + "@angular-eslint/eslint-plugin": "~18.3.0", + "@angular/common": "~18.2.0", + "@angular/core": "~18.2.0", + "@nx/eslint-plugin": "~19.5.0", + "@nx/jest": "~19.5.0", + "@o3r/build-helpers": "workspace:^", + "@o3r/eslint-plugin": "workspace:^", + "@o3r/test-helpers": "workspace:^", + "@schematics/angular": "~18.2.0", + "@stylistic/eslint-plugin-ts": "~2.4.0", + "@swc/cli": "~0.4.0", + "@swc/core": "~1.7.0", + "@types/jest": "~29.5.2", + "@types/node": "^20.0.0", + "@types/uuid": "^9.0.0", + "@typescript-eslint/eslint-plugin": "^7.14.1", + "@typescript-eslint/parser": "^7.14.1", + "@typescript-eslint/utils": "^7.14.1", + "cpy-cli": "^5.0.0", + "eslint": "^8.57.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-plugin-jest": "~28.8.0", + "eslint-plugin-jsdoc": "~48.11.0", + "eslint-plugin-prefer-arrow": "~1.2.3", + "eslint-plugin-unicorn": "^54.0.0", + "jest": "~29.7.0", + "jest-junit": "~16.0.0", + "jsonc-eslint-parser": "~2.4.0", + "minimist": "^1.2.6", + "pid-from-port": "^1.1.3", + "rimraf": "^5.0.1", + "rxjs": "^7.8.1", + "semver": "^7.5.2", + "ts-jest": "~29.2.0", + "ts-node": "~10.9.2", + "type-fest": "^4.10.2", + "typescript": "~5.5.4", + "zone.js": "~0.14.2" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "schematics": "./collection.json" +} diff --git a/packages/@ama-sdk/client-angular/project.json b/packages/@ama-sdk/client-angular/project.json new file mode 100644 index 0000000000..af0f4f8283 --- /dev/null +++ b/packages/@ama-sdk/client-angular/project.json @@ -0,0 +1,84 @@ +{ + "name": "ama-sdk-client-angular", + "$schema": "https://raw.githubusercontent.com/nrwl/nx/master/packages/nx/schemas/project-schema.json", + "projectType": "library", + "sourceRoot": "packages/@ama-sdk/client-angular/src", + "prefix": "o3r", + "targets": { + "build": { + "executor": "nx:run-script", + "outputs": ["{projectRoot}/dist/package.json"], + "options": { + "script": "postbuild" + }, + "dependsOn": [ + "build-builders", + "compile", + "build-esm2015", + "build-cjs" + ] + }, + "build-esm2015": { + "executor": "nx:run-script", + "options": { + "script": "build:esm2015" + } + }, + "build-cjs": { + "executor": "nx:run-script", + "options": { + "script": "build:cjs" + } + }, + "compile": { + "executor": "nx:run-script", + "options": { + "script": "build:esm2020" + }, + "outputs": ["{projectRoot}/dist/src"] + }, + "lint": { + "options": { + "eslintConfig": "packages/@ama-sdk/client-angular/.eslintrc.js", + "lintFilePatterns": [ + "packages/@ama-sdk/client-angular/src/**/*.ts", + "packages/@ama-sdk/client-angular/package.json" + ] + }, + "dependsOn": [ + "build" + ] + }, + "test": { + "executor": "@nx/jest:jest", + "options": { + "jestConfig": "packages/@ama-sdk/client-angular/jest.config.js" + } + }, + "prepare-publish": { + "executor": "nx:run-script", + "options": { + "script": "prepare:publish" + } + }, + "publish": { + "executor": "nx:run-commands", + "options": { + "command": "npm publish packages/@ama-sdk/client-angular/dist" + } + }, + "prepare-build-builders": { + "executor": "nx:run-script", + "options": { + "script": "prepare:build:builders" + } + }, + "build-builders": { + "executor": "nx:run-script", + "options": { + "script": "build:builders" + } + } + }, + "tags": [] +} diff --git a/packages/@ama-sdk/client-angular/schematics/ng-add/index.js b/packages/@ama-sdk/client-angular/schematics/ng-add/index.js new file mode 100644 index 0000000000..31936d01bc --- /dev/null +++ b/packages/@ama-sdk/client-angular/schematics/ng-add/index.js @@ -0,0 +1,13 @@ +/* + +This files is used to allow the usage of the builder within @o3r/framework mono-repository. +It should not be part of the package. + +*/ + +const {resolve} = require('node:path'); + +require('ts-node').register({ project: resolve(__dirname, '..', '..', 'tsconfig.builders.json') }); +require('ts-node').register = () => {}; + +module.exports = require('./index.ts'); diff --git a/packages/@ama-sdk/client-angular/schematics/ng-add/index.ts b/packages/@ama-sdk/client-angular/schematics/ng-add/index.ts new file mode 100644 index 0000000000..a08bf76a55 --- /dev/null +++ b/packages/@ama-sdk/client-angular/schematics/ng-add/index.ts @@ -0,0 +1,83 @@ +import { chain, noop, Rule } from '@angular-devkit/schematics'; +import type { NgAddSchematicsSchema } from './schema'; +import * as path from 'node:path'; +import { NodeDependencyType } from '@schematics/angular/utility/dependencies'; +import { mapMigrationFromCoreImports } from './migration/import-map'; + +const devDependenciesToInstall: string[] = [ + +]; + + +const reportMissingSchematicsDep = (logger: { error: (message: string) => any }) => (reason: any) => { + logger.error(`[ERROR]: Adding @ama-sdk/client-angular has failed. +If the error is related to missing @o3r dependencies you need to install '@o3r/schematics' as devDependency to be able to use this schematics. Please run 'ng add @o3r/schematics'. +Otherwise, use the error message as guidance.`); + throw reason; +}; + +/** + * Add SDk Angular Client to an Otter Project + * @param options + */ +function ngAddFn(options: NgAddSchematicsSchema): Rule { + return async (tree, context) => { + // use dynamic import to properly raise an exception if it is not an Otter project. + const { + getPackageInstallConfig, + applyEsLintFix, + setupDependencies, + getO3rPeerDeps, + getProjectNewDependenciesTypes, + getWorkspaceConfig, + getExternalDependenciesVersionRange, + updateImports + } = await import('@o3r/schematics'); + + const workspaceProject = options.projectName ? getWorkspaceConfig(tree)?.projects[options.projectName] : undefined; + const packageJsonPath = path.resolve(__dirname, '..', '..', 'package.json'); + const depsInfo = getO3rPeerDeps(packageJsonPath); + + const dependencies = depsInfo.o3rPeerDeps.reduce((acc, dep) => { + acc[dep] = { + inManifest: [{ + range: `${options.exactO3rVersion ? '' : '~'}${depsInfo.packageVersion}`, + types: getProjectNewDependenciesTypes(workspaceProject) + }], + ngAddOptions: { exactO3rVersion: options.exactO3rVersion } + }; + return acc; + }, getPackageInstallConfig(packageJsonPath, tree, options.projectName, false, !!options.exactO3rVersion)); + Object.entries(getExternalDependenciesVersionRange(devDependenciesToInstall, packageJsonPath, context.logger)) + .forEach(([dep, range]) => { + dependencies[dep] = { + inManifest: [{ + range, + types: [NodeDependencyType.Dev] + }] + }; + }); + + return chain([ + // optional custom action dedicated to this module + options.skipLinter ? noop() : applyEsLintFix(), + // add the missing Otter modules in the current project + setupDependencies({ + projectName: options.projectName, + dependencies, + ngAddToRun: depsInfo.o3rPeerDeps + }), + + updateImports(mapMigrationFromCoreImports) + ]); + }; +} + +/** + * Add SDk Angular Client to an Otter Project + * @param options + */ +export const ngAdd = (options: NgAddSchematicsSchema): Rule => async (_, { logger }) => { + const { createSchematicWithMetricsIfInstalled } = await import('@o3r/schematics').catch(reportMissingSchematicsDep(logger)); + return createSchematicWithMetricsIfInstalled(ngAddFn)(options); +}; diff --git a/packages/@ama-sdk/client-angular/schematics/ng-add/migration/import-map.ts b/packages/@ama-sdk/client-angular/schematics/ng-add/migration/import-map.ts new file mode 100644 index 0000000000..dae9058be6 --- /dev/null +++ b/packages/@ama-sdk/client-angular/schematics/ng-add/migration/import-map.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +const currentPackage = { + newPackage: '@ama-sdk/client-angular' +}; + +export const mapMigrationFromCoreImports = { + '@ama-sdk/core': [ + 'BaseApiAngularClientOptions', + 'BaseApiAngularClientConstructor', + 'ApiAngularClient', + 'MockInterceptAngular', + 'MockInterceptAngularParameters' + ].reduce((acc, name) => ({...acc, [name]: currentPackage}), {} as Record) +}; diff --git a/packages/@ama-sdk/client-angular/schematics/ng-add/schema.json b/packages/@ama-sdk/client-angular/schematics/ng-add/schema.json new file mode 100644 index 0000000000..f8901ee040 --- /dev/null +++ b/packages/@ama-sdk/client-angular/schematics/ng-add/schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "ngAddSchematicsSchema", + "title": "Add Otter ama-sdk-client-angular ", + "description": "ngAdd Otter ama-sdk-client-angular", + "properties": { + "projectName": { + "type": "string", + "description": "Project name", + "$default": { + "$source": "projectName" + } + } + }, + "additionalProperties": true, + "required": [ + ] +} diff --git a/packages/@ama-sdk/client-angular/schematics/ng-add/schema.ts b/packages/@ama-sdk/client-angular/schematics/ng-add/schema.ts new file mode 100644 index 0000000000..76d3853e73 --- /dev/null +++ b/packages/@ama-sdk/client-angular/schematics/ng-add/schema.ts @@ -0,0 +1,6 @@ +import type { SchematicOptionObject } from '@o3r/schematics'; + +export interface NgAddSchematicsSchema extends SchematicOptionObject { + /** Project name */ + projectName?: string | undefined; +} diff --git a/packages/@ama-sdk/client-angular/src/angular-plugin.ts b/packages/@ama-sdk/client-angular/src/angular-plugin.ts new file mode 100644 index 0000000000..102800cf8e --- /dev/null +++ b/packages/@ama-sdk/client-angular/src/angular-plugin.ts @@ -0,0 +1,44 @@ +import type { Observable } from 'rxjs'; +import type { HttpResponse } from '@angular/common/http'; +import type { ApiClient, PluginContext, RequestOptions } from '@ama-sdk/core'; + +/** + * Interface of an async runnable plugin + */ +export interface PluginObservableRunner { + /** Transformation function */ + transform(data: V): Observable; +} + +/** Angular HTTP Call Response type */ +export type AngularCall = Observable>; + +/** + * Interface of an SDK reply plugin. + * The plugin will be run on the reply of a call + */ +export interface AngularPluginContext extends PluginContext { + /** URL targeted */ + url: string; + + /** List of loaded plugins apply to the Angular HTTP call */ + angularPlugins: PluginObservableRunner, AngularCall>[]; + + /** Api Client processing the call the the API */ + apiClient: ApiClient; + + /** Angular call options */ + requestOptions: RequestOptions; +} + +/** + * Interface of a Angular plugin. + * The plugin will be run around the Angular Http call + */ +export interface AngularPlugin { + /** + * Load the plugin with the context + * @param context Context of Angular plugin + */ + load(context: AngularPluginContext): PluginObservableRunner, AngularCall>; +} diff --git a/packages/@ama-sdk/client-angular/src/api-angular-client.ts b/packages/@ama-sdk/client-angular/src/api-angular-client.ts new file mode 100644 index 0000000000..aa9fe9408a --- /dev/null +++ b/packages/@ama-sdk/client-angular/src/api-angular-client.ts @@ -0,0 +1,195 @@ +import type { HttpClient, HttpResponse } from '@angular/common/http'; +import type { + ApiClient, + ApiTypes, + BaseApiClientOptions, + PartialExcept, + RequestOptions, + RequestOptionsParameters, + ReviverType, + TokenizedOptions +} from '@ama-sdk/core'; +import { + EmptyResponseError, + ExceptionReply, + extractQueryParams, + filterUndefinedValues, + getResponseReviver, + prepareUrl, + processFormData, + ReviverReply, + tokenizeRequestOptions +} from '@ama-sdk/core'; +import type { AngularCall, AngularPlugin, PluginObservableRunner } from './angular-plugin'; + +/** @see BaseApiClientOptions */ +export interface BaseApiAngularClientOptions extends BaseApiClientOptions { + /** Angular HTTP Client */ + httpClient: HttpClient; + /** List of plugins to apply to the Angular Http call */ + angularPlugins: AngularPlugin[]; +} + +/** @see BaseApiConstructor */ +export interface BaseApiAngularClientConstructor extends PartialExcept { +} + +const DEFAULT_OPTIONS: Omit = { + replyPlugins: [new ReviverReply(), new ExceptionReply()], + angularPlugins: [], + requestPlugins: [], + enableTokenization: false, + disableFallback: false +}; + +/** Client to process the call to the API using Angular API */ +export class ApiAngularClient implements ApiClient { + + /** @inheritdoc */ + public options: BaseApiAngularClientOptions; + + /** + * Initialize your API Client instance + * @param options Configuration of the API Client + */ + constructor(options: BaseApiAngularClientConstructor) { + this.options = { + ...DEFAULT_OPTIONS, + ...options + }; + } + + /** @inheritdoc */ + public extractQueryParams(data: T, names: (keyof T)[]): { [p in keyof T]: string; } { + return extractQueryParams(data, names); + } + + /** @inheritdoc */ + public async getRequestOptions(requestOptionsParameters: RequestOptionsParameters): Promise { + let opts: RequestOptions = { + ...requestOptionsParameters, + headers: new Headers(filterUndefinedValues(requestOptionsParameters.headers)), + queryParams: filterUndefinedValues(requestOptionsParameters.queryParams) + }; + if (this.options.requestPlugins) { + for (const plugin of this.options.requestPlugins) { + opts = await plugin.load({ + logger: this.options.logger, + apiName: requestOptionsParameters.api?.apiName + }).transform(opts); + } + } + + return opts; + } + + /** @inheritdoc */ + public prepareUrl(url: string, queryParameters: { [key: string]: string | undefined } = {}) { + return prepareUrl(url, queryParameters); + } + + /** @inheritdoc */ + public processFormData(data: any, type: string) { + return processFormData(data, type); + } + + /** @inheritdoc */ + public tokenizeRequestOptions(url: string, queryParameters: { [key: string]: string }, piiParamTokens: { [key: string]: string }, data: any): TokenizedOptions | undefined { + return this.options.enableTokenization ? tokenizeRequestOptions(url, queryParameters, piiParamTokens, data) : undefined; + } + + /** @inheritdoc */ + public async processCall(url: string, options: RequestOptions, apiType: ApiTypes | string, apiName: string, revivers?: undefined, operationId?: string): Promise; + public async processCall(url: string, options: RequestOptions, apiType: ApiTypes, apiName: string, revivers: ReviverType | { [statusCode: number]: ReviverType | undefined }, + operationId?: string): Promise; + public async processCall(url: string, options: RequestOptions, apiType: ApiTypes | string, apiName: string, + revivers?: ReviverType | undefined | { [statusCode: number]: ReviverType | undefined }, operationId?: string): Promise { + + let response: HttpResponse | undefined; + let root: any; + let exception: Error | undefined; + + const origin = options.headers.get('Origin'); + + // Execute call + try { + const headers = Object.fromEntries(options.headers.entries()); + + const asyncResponse = new Promise>((resolve, reject) => { + let data: HttpResponse; + const metadataSignal = options.metadata?.signal; + metadataSignal?.throwIfAborted(); + + const loadedPlugins: (PluginObservableRunner, AngularCall>)[] = []; + if (this.options.angularPlugins) { + loadedPlugins.push(...this.options.angularPlugins.map((plugin) => plugin.load({ + angularPlugins: loadedPlugins, + apiClient: this, + url, + apiName, + requestOptions: options, + logger: this.options.logger + }))); + } + + let httpRequest = this.options.httpClient.request(options.method, url, { + ...options, + observe: 'response', + headers + }); + + for (const plugin of loadedPlugins) { + httpRequest = plugin.transform(httpRequest); + } + + const subscription = httpRequest.subscribe({ + next: (res) => data = res, + error: (err) => reject(err), + complete: () => resolve(data) + }); + metadataSignal?.throwIfAborted(); + metadataSignal?.addEventListener('abort', () => { + subscription.unsubscribe(); + reject(metadataSignal.reason); + }); + }); + response = await asyncResponse; + root = response.body; + } catch (e: any) { + exception = new EmptyResponseError(e.message || 'Fail to Fetch', undefined, { apiName, operationId, url, origin }); + } + + // eslint-disable-next-line no-console + const reviver = getResponseReviver(revivers, response, operationId, { disableFallback: this.options.disableFallback, log: console.error }); + const replyPlugins = this.options.replyPlugins ? + this.options.replyPlugins.map((plugin) => plugin.load({ + dictionaries: root && root.dictionaries, + response: response && { + ...response, + headers: new Headers( + response.headers.keys() + .map((key) => ([key, response.headers.get(key)!] as [string, string])) + ) + }, + reviver, + apiType, + apiName, + exception, + operationId, + url, + origin, + logger: this.options.logger + })) : []; + + let parsedData = root; + for (const pluginRunner of replyPlugins) { + parsedData = await pluginRunner.transform(parsedData); + } + + if (exception) { + throw exception; + } + + return parsedData; + } +} diff --git a/packages/@ama-sdk/client-angular/src/index.spec.ts b/packages/@ama-sdk/client-angular/src/index.spec.ts new file mode 100644 index 0000000000..f85b25ec7b --- /dev/null +++ b/packages/@ama-sdk/client-angular/src/index.spec.ts @@ -0,0 +1,5 @@ +// TODO be removed as soon as we have one test in this package + +it('should be removed as soon as we have one test in this package', () => { + expect(true).toBe(true); +}); diff --git a/packages/@ama-sdk/client-angular/src/plugins/index.ts b/packages/@ama-sdk/client-angular/src/plugins/index.ts new file mode 100644 index 0000000000..152b0e6e3d --- /dev/null +++ b/packages/@ama-sdk/client-angular/src/plugins/index.ts @@ -0,0 +1 @@ +export * from './mock-intercept/index'; diff --git a/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/README.md b/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/README.md new file mode 100644 index 0000000000..c8d2798dbf --- /dev/null +++ b/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/README.md @@ -0,0 +1,28 @@ +# Mock intercept plugin + +The mock interception strategy works based on two interceptions: request and fetch. For each interception, a plugin has been made. + +The mock mechanism provides, via the `getResponse` function, a way to completely override the fetch response. To apply the mock at FetchAPI level, we provide the `MockInterceptAngular`. +It will work with the `MockInterceptRequest` on the same mock set. + +Example of usage: + +```typescript +const baseConfig = new ApiAngularClient({ + basePath: 'http://my-api.com', + requestPlugins: [ + new MockInterceptRequest({ + adapter: myAdapter + }) + ], + fetchPlugins: [ + new MockInterceptAngular({ + adapter: myAdapter + }) + ] +}); +``` + +## References + +- [Request Plugin](https://github.com/AmadeusITGroup/otter/blob/main/packages/%40ama-sdk/core/src/plugins/mock-intercept/README.md): full mock mechanism documentation. diff --git a/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/index.ts b/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/index.ts new file mode 100644 index 0000000000..a89522ab45 --- /dev/null +++ b/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/index.ts @@ -0,0 +1,2 @@ +export * from './mock-intercept.angular'; +export * from './mock-intercept.interface'; diff --git a/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/mock-intercept.angular.ts b/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/mock-intercept.angular.ts new file mode 100644 index 0000000000..a26ee1a135 --- /dev/null +++ b/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/mock-intercept.angular.ts @@ -0,0 +1,76 @@ +import { delay, from, mergeMap } from 'rxjs'; +import type { AngularCall, AngularPlugin, AngularPluginContext, PluginObservableRunner } from '../../angular-plugin'; +import { CUSTOM_MOCK_OPERATION_ID_HEADER, MockInterceptRequest } from '@ama-sdk/core'; +import { HttpResponse } from '@angular/common/http'; +import type { MockInterceptAngularParameters } from './mock-intercept.interface'; + +/** + * Plugin to mock and intercept the call of SDK + * + * This plugin should be used only with the MockInterceptRequest Plugin. + * It will allow the user to delay the response or to handle the getResponse function provided with the mock (if present). + */ +export class MockInterceptAngular implements AngularPlugin { + + constructor(protected options: MockInterceptAngularParameters) {} + + public load(context: AngularPluginContext): PluginObservableRunner, AngularCall> { + + if (!context.apiClient.options.requestPlugins.some((plugin) => plugin instanceof MockInterceptRequest)) { + throw new Error('MockInterceptAngular plugin should be used only with the MockInterceptRequest plugin'); + } + + return { + transform: (call: AngularCall) => { + return from(( + async () => { + await this.options.adapter.initialize(); + + let originalCall = call; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + if (!context.options.headers || !(context.options.headers instanceof Headers) || !(context.options.headers as Headers).has(CUSTOM_MOCK_OPERATION_ID_HEADER)) { + return originalCall; + } + + if (typeof this.options.delayTiming !== 'undefined') { + const delayTime = typeof this.options.delayTiming === 'number' ? this.options.delayTiming : await this.options.delayTiming({ + ...context, + fetchPlugins: [], + options: context.requestOptions + }); + originalCall = originalCall.pipe(delay(delayTime)); + } + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + const operationId = (context.options.headers as Headers).get(CUSTOM_MOCK_OPERATION_ID_HEADER)!; + try { + const mock = this.options.adapter.getLatestMock(operationId); + + if (!mock.getResponse) { + return originalCall; + } + + const response = mock.getResponse(); + return originalCall.pipe( + mergeMap(async (res) => { + const body = await response.json(); + const responseCloned = res.clone(); + return new HttpResponse({ + ...responseCloned, + body, + url: responseCloned.url || undefined + }); + }) + ); + + } catch { + (context.logger || console).error(`Failed to retrieve the latest mock for Operation ID ${operationId}, fallback to default mock`); + return originalCall; + } + })() + ).pipe(mergeMap((res) => res)); + } + }; + } + +} diff --git a/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/mock-intercept.interface.ts b/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/mock-intercept.interface.ts new file mode 100644 index 0000000000..169d1ea4f2 --- /dev/null +++ b/packages/@ama-sdk/client-angular/src/plugins/mock-intercept/mock-intercept.interface.ts @@ -0,0 +1,12 @@ +import type { MockAdapter } from '@ama-sdk/core'; +import type { AngularPluginContext } from '../../angular-plugin'; + +/** + * Mock Angular Plugin options + */ +export interface MockInterceptAngularParameters { + /** List of mocks to be used */ + adapter: MockAdapter; + /** Delays the mock response, in milliseconds */ + delayTiming?: number | ((context: AngularPluginContext) => number | Promise); +} diff --git a/packages/@ama-sdk/client-angular/src/public_api.ts b/packages/@ama-sdk/client-angular/src/public_api.ts new file mode 100644 index 0000000000..312a9048f7 --- /dev/null +++ b/packages/@ama-sdk/client-angular/src/public_api.ts @@ -0,0 +1,3 @@ +export * from './api-angular-client'; +export * from './angular-plugin'; +export * from './plugins/index'; diff --git a/packages/@ama-sdk/client-angular/testing/jest.config.ut.builders.js b/packages/@ama-sdk/client-angular/testing/jest.config.ut.builders.js new file mode 100644 index 0000000000..ed52ef11a8 --- /dev/null +++ b/packages/@ama-sdk/client-angular/testing/jest.config.ut.builders.js @@ -0,0 +1,15 @@ +const path = require('node:path'); +const getJestProjectConfig = require('../../../../jest.config.ut').getJestProjectConfig; +const rootDir = path.join(__dirname, '..'); + +/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ +module.exports = { + ...getJestProjectConfig(rootDir, false), + displayName: `${require('../package.json').name}/builders`, + rootDir, + testPathIgnorePatterns: [ + '/.*/templates/.*', + '/src/.*', + '\\.it\\.spec\\.ts$' + ] +}; diff --git a/packages/@ama-sdk/client-angular/testing/jest.config.ut.js b/packages/@ama-sdk/client-angular/testing/jest.config.ut.js new file mode 100644 index 0000000000..a977528613 --- /dev/null +++ b/packages/@ama-sdk/client-angular/testing/jest.config.ut.js @@ -0,0 +1,21 @@ +const path = require('node:path'); +const getJestProjectConfig = require('../../../../jest.config.ut').getJestProjectConfig; +const rootDir = path.join(__dirname, '..'); + +/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ +module.exports = { + ...getJestProjectConfig(rootDir, false), + displayName: require('../package.json').name, + rootDir, + fakeTimers: { + enableGlobally: true, + // TODO re-enable fake dates when issue fixed https://github.com/sinonjs/fake-timers/issues/437 and v12 is used by Jest + doNotFake: ['Date'] + }, + testPathIgnorePatterns: [ + '/.*/templates/.*', + '/builders/.*', + '/schematics/.*', + '\\.it\\.spec\\.ts$' + ] +}; diff --git a/packages/@ama-sdk/client-angular/testing/setup-jest.ts b/packages/@ama-sdk/client-angular/testing/setup-jest.ts new file mode 100644 index 0000000000..42c586a2d8 --- /dev/null +++ b/packages/@ama-sdk/client-angular/testing/setup-jest.ts @@ -0,0 +1,2 @@ +import '@o3r/test-helpers/setup-jest-builders'; + diff --git a/packages/@ama-sdk/client-angular/tsconfig.build.json b/packages/@ama-sdk/client-angular/tsconfig.build.json new file mode 100644 index 0000000000..e76c615d1c --- /dev/null +++ b/packages/@ama-sdk/client-angular/tsconfig.build.json @@ -0,0 +1,27 @@ +{ + "extends": "../../../tsconfig.build", + "compilerOptions": { + "sourceMap": true, + "incremental": true, + "composite": true, + "rootDir": ".", + "lib": [ + "dom", + "dom.iterable", + "scripthost", + "es2017.object", + "esnext" + ], + "declarationMap": true, + "target": "es2020", + "module": "es2020", + "tsBuildInfoFile": "./build/tsconfig.tsbuildinfo", + "outDir": "./dist" + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "**/*.spec.ts" + ] +} diff --git a/packages/@ama-sdk/client-angular/tsconfig.builders.json b/packages/@ama-sdk/client-angular/tsconfig.builders.json new file mode 100644 index 0000000000..eb1c38fbee --- /dev/null +++ b/packages/@ama-sdk/client-angular/tsconfig.builders.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.build", + "compilerOptions": { + "incremental": true, + "composite": true, + "outDir": "./dist", + "module": "CommonJS", + "rootDir": ".", + "tsBuildInfoFile": "build/.tsbuildinfo.builders" + }, + "include": [ + "schematics/**/*.ts" + ], + "exclude": [ + "**/*.spec.ts", + "schematics/**/templates/**", + "schematics/ng-add/mocks/**" + ] +} diff --git a/packages/@ama-sdk/client-angular/tsconfig.eslint.json b/packages/@ama-sdk/client-angular/tsconfig.eslint.json new file mode 100644 index 0000000000..38eec16702 --- /dev/null +++ b/packages/@ama-sdk/client-angular/tsconfig.eslint.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig", + "include": [ + ".eslintrc.js", + "jest.config.js", + "testing/*", + "scripts/*" + ] +} diff --git a/packages/@ama-sdk/client-angular/tsconfig.json b/packages/@ama-sdk/client-angular/tsconfig.json new file mode 100644 index 0000000000..2a565f391f --- /dev/null +++ b/packages/@ama-sdk/client-angular/tsconfig.json @@ -0,0 +1,12 @@ +/* IDE usage only */ +{ + "extends": "../../../tsconfig.base", + "references": [ + { + "path": "./tsconfig.spec.json" + }, + { + "path": "./tsconfig.build.json" + } + ] +} diff --git a/packages/@ama-sdk/client-angular/tsconfig.spec.json b/packages/@ama-sdk/client-angular/tsconfig.spec.json new file mode 100644 index 0000000000..cf096bfec9 --- /dev/null +++ b/packages/@ama-sdk/client-angular/tsconfig.spec.json @@ -0,0 +1,25 @@ +{ + "extends": "../../../tsconfig.jest", + "compilerOptions": { + "esModuleInterop": true, + "strictNullChecks": false, + "strictPropertyInitialization":false, + "noImplicitAny": false, + "incremental": true, + "composite": true, + "declarationMap": false, + "strict": false, + "module": "ES2020", + "target": "ES2020" + }, + "include": [ + "./**/*.spec.ts" + ], + "exclude": [ + ], + "references": [ + { + "path": "./tsconfig.build.json" + } + ] +} diff --git a/packages/@ama-sdk/client-beacon/.eslintrc.js b/packages/@ama-sdk/client-beacon/.eslintrc.js new file mode 100644 index 0000000000..f5ca5f3878 --- /dev/null +++ b/packages/@ama-sdk/client-beacon/.eslintrc.js @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable quote-props */ + +module.exports = { + 'root': true, + 'parserOptions': { + 'tsconfigRootDir': __dirname, + 'project': [ + 'tsconfig.build.json', + 'tsconfig.builders.json', + 'tsconfig.spec.json', + 'tsconfig.eslint.json' + ], + 'sourceType': 'module' + }, + 'extends': [ + '../../../.eslintrc.js' + ] +}; diff --git a/packages/@ama-sdk/client-beacon/.gitignore b/packages/@ama-sdk/client-beacon/.gitignore new file mode 100644 index 0000000000..03eb2be86c --- /dev/null +++ b/packages/@ama-sdk/client-beacon/.gitignore @@ -0,0 +1,10 @@ +/fwk +/helpers +/index.* +/bundles +/test +/dist-test +/plugins +/dist +/utils +/build diff --git a/packages/@ama-sdk/client-beacon/.npmignore b/packages/@ama-sdk/client-beacon/.npmignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/@ama-sdk/client-beacon/README.md b/packages/@ama-sdk/client-beacon/README.md new file mode 100644 index 0000000000..a92c19778f --- /dev/null +++ b/packages/@ama-sdk/client-beacon/README.md @@ -0,0 +1,22 @@ +# Ama SDK Beacon Client + +[![Stable Version](https://img.shields.io/npm/v/@ama-sdk/client-beacon?style=for-the-badge)](https://www.npmjs.com/package/@ama-sdk/client-beacon) +[![Bundle Size](https://img.shields.io/bundlephobia/min/@ama-sdk/client-beacon?color=green&style=for-the-badge)](https://www.npmjs.com/package/@ama-sdk/client-beacon) + +This package exposes the **Api Beacon Client** from an SDK based on [@ama-sdk/core](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/core). + +This package contains all the [Beacon Plugins](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/client-beacon/src/plugins), helpers and object definitions to dialog with an API following the `ama-sdk` architecture. + +> [!TIP] +> Please refer to the [SDK initializer](https://www.npmjs.com/package/@ama-sdk/create) package for getting started with an API client SDK based on `ama-sdk` architecture. + +## Setup + +The **Api Beacon Client** can be added to your project via the following command: + +```shell +ng add @ama-sdk/client-beacon +``` + +> [!NOTE] +> In case of migration from deprecated `ApiBeaconClient` imported from `@ama-sdk/core`, the `ng add` command will replace, in your existing code, the import from `@ama-sdk/core` to `@ama-sdk/client-beacon` for the deprecated dependencies. diff --git a/packages/@ama-sdk/client-beacon/collection.json b/packages/@ama-sdk/client-beacon/collection.json new file mode 100644 index 0000000000..2d9b7aea8f --- /dev/null +++ b/packages/@ama-sdk/client-beacon/collection.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/angular/angular-cli/main/packages/angular_devkit/schematics/collection-schema.json", + "schematics": { + "ng-add": { + "description": "Add the SDK Beacon Client API to the project.", + "factory": "./schematics/ng-add/index#ngAdd", + "schema": "./schematics/ng-add/schema.json", + "aliases": ["install", "i"] + } + } +} diff --git a/packages/@ama-sdk/client-beacon/jest.config.js b/packages/@ama-sdk/client-beacon/jest.config.js new file mode 100644 index 0000000000..237fd70fc2 --- /dev/null +++ b/packages/@ama-sdk/client-beacon/jest.config.js @@ -0,0 +1,10 @@ +const getJestGlobalConfig = require('../../../jest.config.ut').getJestGlobalConfig; + +/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ +module.exports = { + ...getJestGlobalConfig(), + projects: [ + '/testing/jest.config.ut.js', + '/testing/jest.config.ut.builders.js' + ] +}; diff --git a/packages/@ama-sdk/client-beacon/package.json b/packages/@ama-sdk/client-beacon/package.json new file mode 100644 index 0000000000..a0ac68e89a --- /dev/null +++ b/packages/@ama-sdk/client-beacon/package.json @@ -0,0 +1,122 @@ +{ + "name": "@ama-sdk/client-beacon", + "version": "0.0.0-placeholder", + "publishConfig": { + "access": "public" + }, + "description": "Beacon API Request client for @ama-sdk/core based SDK", + "module": "dist/src/public_api.js", + "esm2015": "dist/esm2015/public_api.js", + "esm2020": "dist/src/public_api.js", + "typings": "dist/src/public_api.d.ts", + "sideEffects": false, + "exports": { + "./package.json": { + "default": "./package.json" + }, + ".": { + "module": "./dist/src/public_api.js", + "esm2020": "./dist/src/public_api.js", + "esm2015": "./dist/esm2015/public_api.js", + "es2020": "./dist/cjs/public_api.js", + "default": "./dist/cjs/public_api.js", + "typings": "./dist/src/public_api.d.ts", + "import": "./dist/src/public_api.js", + "node": "./dist/cjs/public_api.js", + "require": "./dist/cjs/public_api.js" + } + }, + "scripts": { + "nx": "nx", + "ng": "yarn nx", + "build": "yarn nx build ama-sdk-client-beacon", + "build:cjs": "swc src -d dist/cjs -C module.type=commonjs -q --strip-leading-paths", + "build:esm2015": "swc src -d dist/esm2015 -C module.type=es6 -q --strip-leading-paths", + "build:esm2020": "tsc -b tsconfig.build.json", + "postbuild": "yarn cpy './package.json' dist && patch-package-json-main", + "prepare:build:builders": "yarn cpy 'schematics/**/*.json' dist/schematics && yarn cpy 'collection.json' dist", + "build:builders": "tsc -b tsconfig.builders.json --pretty && yarn generate-cjs-manifest", + "prepare:publish": "prepare-publish ./dist" + }, + "dependencies": { + "@swc/helpers": "~0.5.0", + "tslib": "^2.6.2", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "@ama-sdk/core": "workspace:^", + "@angular-devkit/schematics": "~18.2.0", + "@angular/cli": "~18.2.0", + "@angular/common": "~18.2.0", + "@o3r/schematics": "workspace:^", + "@schematics/angular": "~18.2.0", + "typescript": "~5.5.4" + }, + "peerDependenciesMeta": { + "@angular-devkit/schematics": { + "optional": true + }, + "@angular/cli": { + "optional": true + }, + "@angular/common": { + "optional": true + }, + "@o3r/schematics": { + "optional": true + }, + "@schematics/angular": { + "optional": true + }, + "typescript": { + "optional": true + } + }, + "devDependencies": { + "@ama-sdk/core": "workspace:^", + "@angular-devkit/core": "~18.2.0", + "@angular-devkit/schematics": "~18.2.0", + "@angular-eslint/eslint-plugin": "~18.3.0", + "@angular/common": "~18.2.0", + "@angular/core": "~18.2.0", + "@nx/eslint-plugin": "~19.5.0", + "@nx/jest": "~19.5.0", + "@o3r/build-helpers": "workspace:^", + "@o3r/eslint-plugin": "workspace:^", + "@o3r/test-helpers": "workspace:^", + "@schematics/angular": "~18.2.0", + "@stylistic/eslint-plugin-ts": "~2.4.0", + "@swc/cli": "~0.4.0", + "@swc/core": "~1.7.0", + "@types/jest": "~29.5.2", + "@types/node": "^20.0.0", + "@types/uuid": "^9.0.0", + "@typescript-eslint/eslint-plugin": "^7.14.1", + "@typescript-eslint/parser": "^7.14.1", + "@typescript-eslint/utils": "^7.14.1", + "cpy-cli": "^5.0.0", + "eslint": "^8.57.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-plugin-jest": "~28.8.0", + "eslint-plugin-jsdoc": "~48.11.0", + "eslint-plugin-prefer-arrow": "~1.2.3", + "eslint-plugin-unicorn": "^54.0.0", + "jest": "~29.7.0", + "jest-junit": "~16.0.0", + "jsonc-eslint-parser": "~2.4.0", + "minimist": "^1.2.6", + "pid-from-port": "^1.1.3", + "rimraf": "^5.0.1", + "rxjs": "^7.8.1", + "semver": "^7.5.2", + "ts-jest": "~29.2.0", + "ts-node": "~10.9.2", + "type-fest": "^4.10.2", + "typescript": "~5.5.4", + "zone.js": "~0.14.2" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "schematics": "./collection.json" +} diff --git a/packages/@ama-sdk/client-beacon/project.json b/packages/@ama-sdk/client-beacon/project.json new file mode 100644 index 0000000000..bc4f4bd422 --- /dev/null +++ b/packages/@ama-sdk/client-beacon/project.json @@ -0,0 +1,84 @@ +{ + "name": "ama-sdk-client-beacon", + "$schema": "https://raw.githubusercontent.com/nrwl/nx/master/packages/nx/schemas/project-schema.json", + "projectType": "library", + "sourceRoot": "packages/@ama-sdk/client-beacon/src", + "prefix": "o3r", + "targets": { + "build": { + "executor": "nx:run-script", + "outputs": ["{projectRoot}/dist/package.json"], + "options": { + "script": "postbuild" + }, + "dependsOn": [ + "build-builders", + "compile", + "build-esm2015", + "build-cjs" + ] + }, + "build-esm2015": { + "executor": "nx:run-script", + "options": { + "script": "build:esm2015" + } + }, + "build-cjs": { + "executor": "nx:run-script", + "options": { + "script": "build:cjs" + } + }, + "compile": { + "executor": "nx:run-script", + "options": { + "script": "build:esm2020" + }, + "outputs": ["{projectRoot}/dist/src"] + }, + "lint": { + "options": { + "eslintConfig": "packages/@ama-sdk/client-beacon/.eslintrc.js", + "lintFilePatterns": [ + "packages/@ama-sdk/client-beacon/src/**/*.ts", + "packages/@ama-sdk/client-beacon/package.json" + ] + }, + "dependsOn": [ + "build" + ] + }, + "test": { + "executor": "@nx/jest:jest", + "options": { + "jestConfig": "packages/@ama-sdk/client-beacon/jest.config.js" + } + }, + "prepare-publish": { + "executor": "nx:run-script", + "options": { + "script": "prepare:publish" + } + }, + "publish": { + "executor": "nx:run-commands", + "options": { + "command": "npm publish packages/@ama-sdk/client-beacon/dist" + } + }, + "prepare-build-builders": { + "executor": "nx:run-script", + "options": { + "script": "prepare:build:builders" + } + }, + "build-builders": { + "executor": "nx:run-script", + "options": { + "script": "build:builders" + } + } + }, + "tags": [] +} diff --git a/packages/@ama-sdk/client-beacon/schematics/ng-add/index.js b/packages/@ama-sdk/client-beacon/schematics/ng-add/index.js new file mode 100644 index 0000000000..31936d01bc --- /dev/null +++ b/packages/@ama-sdk/client-beacon/schematics/ng-add/index.js @@ -0,0 +1,13 @@ +/* + +This files is used to allow the usage of the builder within @o3r/framework mono-repository. +It should not be part of the package. + +*/ + +const {resolve} = require('node:path'); + +require('ts-node').register({ project: resolve(__dirname, '..', '..', 'tsconfig.builders.json') }); +require('ts-node').register = () => {}; + +module.exports = require('./index.ts'); diff --git a/packages/@ama-sdk/client-beacon/schematics/ng-add/index.ts b/packages/@ama-sdk/client-beacon/schematics/ng-add/index.ts new file mode 100644 index 0000000000..095fad361c --- /dev/null +++ b/packages/@ama-sdk/client-beacon/schematics/ng-add/index.ts @@ -0,0 +1,83 @@ +import { chain, noop, Rule } from '@angular-devkit/schematics'; +import type { NgAddSchematicsSchema } from './schema'; +import * as path from 'node:path'; +import { NodeDependencyType } from '@schematics/angular/utility/dependencies'; +import { mapMigrationFromCoreImports } from './migration/import-map'; + +const devDependenciesToInstall: string[] = [ + +]; + + +const reportMissingSchematicsDep = (logger: { error: (message: string) => any }) => (reason: any) => { + logger.error(`[ERROR]: Adding @ama-sdk/client-beacon has failed. +If the error is related to missing @o3r dependencies you need to install '@o3r/schematics' as devDependency to be able to use this schematics. Please run 'ng add @o3r/schematics'. +Otherwise, use the error message as guidance.`); + throw reason; +}; + +/** + * Add SDk Beacon Client to an Otter Project + * @param options + */ +function ngAddFn(options: NgAddSchematicsSchema): Rule { + return async (tree, context) => { + // use dynamic import to properly raise an exception if it is not an Otter project. + const { + getPackageInstallConfig, + applyEsLintFix, + setupDependencies, + getO3rPeerDeps, + getProjectNewDependenciesTypes, + getWorkspaceConfig, + getExternalDependenciesVersionRange, + updateImports + } = await import('@o3r/schematics'); + + const workspaceProject = options.projectName ? getWorkspaceConfig(tree)?.projects[options.projectName] : undefined; + const packageJsonPath = path.resolve(__dirname, '..', '..', 'package.json'); + const depsInfo = getO3rPeerDeps(packageJsonPath); + + const dependencies = depsInfo.o3rPeerDeps.reduce((acc, dep) => { + acc[dep] = { + inManifest: [{ + range: `${options.exactO3rVersion ? '' : '~'}${depsInfo.packageVersion}`, + types: getProjectNewDependenciesTypes(workspaceProject) + }], + ngAddOptions: { exactO3rVersion: options.exactO3rVersion } + }; + return acc; + }, getPackageInstallConfig(packageJsonPath, tree, options.projectName, false, !!options.exactO3rVersion)); + Object.entries(getExternalDependenciesVersionRange(devDependenciesToInstall, packageJsonPath, context.logger)) + .forEach(([dep, range]) => { + dependencies[dep] = { + inManifest: [{ + range, + types: [NodeDependencyType.Dev] + }] + }; + }); + + return chain([ + // optional custom action dedicated to this module + options.skipLinter ? noop() : applyEsLintFix(), + // add the missing Otter modules in the current project + setupDependencies({ + projectName: options.projectName, + dependencies, + ngAddToRun: depsInfo.o3rPeerDeps + }), + + updateImports(mapMigrationFromCoreImports) + ]); + }; +} + +/** + * Add SDk Beacon Client to an Otter Project + * @param options + */ +export const ngAdd = (options: NgAddSchematicsSchema): Rule => async (_, { logger }) => { + const { createSchematicWithMetricsIfInstalled } = await import('@o3r/schematics').catch(reportMissingSchematicsDep(logger)); + return createSchematicWithMetricsIfInstalled(ngAddFn)(options); +}; diff --git a/packages/@ama-sdk/client-beacon/schematics/ng-add/migration/import-map.ts b/packages/@ama-sdk/client-beacon/schematics/ng-add/migration/import-map.ts new file mode 100644 index 0000000000..dfb712bc6b --- /dev/null +++ b/packages/@ama-sdk/client-beacon/schematics/ng-add/migration/import-map.ts @@ -0,0 +1,12 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +const currentPackage = { + newPackage: '@ama-sdk/client-beacon' +}; + +export const mapMigrationFromCoreImports = { + '@ama-sdk/core': [ + 'BaseApiBeaconClientOptions', + 'BaseApiBeaconClientConstructor', + 'ApiBeaconClient' + ].reduce((acc, name) => ({...acc, [name]: currentPackage}), {} as Record) +}; diff --git a/packages/@ama-sdk/client-beacon/schematics/ng-add/schema.json b/packages/@ama-sdk/client-beacon/schematics/ng-add/schema.json new file mode 100644 index 0000000000..fd66879794 --- /dev/null +++ b/packages/@ama-sdk/client-beacon/schematics/ng-add/schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "ngAddSchematicsSchema", + "title": "Add Otter ama-sdk-client-beacon ", + "description": "ngAdd Otter ama-sdk-client-beacon", + "properties": { + "projectName": { + "type": "string", + "description": "Project name", + "$default": { + "$source": "projectName" + } + } + }, + "additionalProperties": true, + "required": [ + ] +} diff --git a/packages/@ama-sdk/client-beacon/schematics/ng-add/schema.ts b/packages/@ama-sdk/client-beacon/schematics/ng-add/schema.ts new file mode 100644 index 0000000000..76d3853e73 --- /dev/null +++ b/packages/@ama-sdk/client-beacon/schematics/ng-add/schema.ts @@ -0,0 +1,6 @@ +import type { SchematicOptionObject } from '@o3r/schematics'; + +export interface NgAddSchematicsSchema extends SchematicOptionObject { + /** Project name */ + projectName?: string | undefined; +} diff --git a/packages/@ama-sdk/client-beacon/src/api-beacon-client.ts b/packages/@ama-sdk/client-beacon/src/api-beacon-client.ts new file mode 100644 index 0000000000..35b598a81f --- /dev/null +++ b/packages/@ama-sdk/client-beacon/src/api-beacon-client.ts @@ -0,0 +1,127 @@ +import type { + ApiClient, + ApiTypes, + BaseApiClientOptions, + PartialExcept, + RequestOptions, + RequestOptionsParameters, + TokenizedOptions +} from '@ama-sdk/core'; +import { + extractQueryParams, + filterUndefinedValues, + prepareUrl, + processFormData, + tokenizeRequestOptions +} from '@ama-sdk/core'; + +/** @see BaseApiClientOptions */ +export interface BaseApiBeaconClientOptions extends BaseApiClientOptions { + /** @inheritdoc */ + replyPlugins: never[]; +} + +/** @see BaseApiConstructor */ +export interface BaseApiBeaconClientConstructor extends PartialExcept, 'basePath'> { +} + +const DEFAULT_OPTIONS: Omit = { + replyPlugins: [] as never[], + requestPlugins: [], + enableTokenization: false +}; + +/** + * Determine if the given value is a promise + * @param value The value to test + */ +// NOTE: the `extends unknown` is required for ESM build with TSC +// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint +const isPromise = (value: T | Promise): value is Promise => value instanceof Promise; + +/** + * The Beacon API client is an implementation of the API Client using the Navigator Beacon API. + * The Beacon API is a low-level API that allows you to send message synchronously. It can be used to send request on window unload or before unload events. + */ +export class ApiBeaconClient implements ApiClient { + + /** @inheritdoc */ + public options: BaseApiBeaconClientOptions; + + /** + * Initialize your API Client instance + * @param options Configuration of the API Client + */ + constructor(options: BaseApiBeaconClientConstructor) { + + if (typeof navigator === 'undefined' || !navigator.sendBeacon) { + throw new Error('Beacon API is not supported in this context'); + } + + this.options = { + ...DEFAULT_OPTIONS, + ...options + }; + } + + /** @inheritdoc */ + public extractQueryParams(data: T, names: (keyof T)[]): { [p in keyof T]: string; } { + return extractQueryParams(data, names); + } + + /** @inheritdoc */ + public getRequestOptions(options: RequestOptionsParameters): Promise { + + if (options.method.toUpperCase() !== 'POST') { + throw new Error(`Unsupported method: ${options.method}. The beacon API only supports POST.`); + } + + let opts: RequestOptions = { + ...options, + headers: new Headers(filterUndefinedValues(options.headers)), + queryParams: filterUndefinedValues(options.queryParams) + }; + if (this.options.requestPlugins) { + for (const plugin of this.options.requestPlugins) { + const changedOpt = plugin.load({ logger: this.options.logger, apiName: options.api?.apiName }).transform(opts); + if (isPromise(changedOpt)) { + throw new Error(`Request plugin ${plugin.constructor.name} has async transform method. Only sync methods are supported with the Beacon client.`); + } else { + opts = changedOpt; + } + } + } + + return Promise.resolve(opts); + } + + /** @inheritdoc */ + public prepareUrl(url: string, queryParameters?: { [key: string]: string }): string { + return prepareUrl(url, queryParameters); + } + + /** @inheritdoc */ + public tokenizeRequestOptions(url: string, queryParameters: { [key: string]: string }, piiParamTokens: { [key: string]: string }, data: any): TokenizedOptions | undefined { + return this.options.enableTokenization ? tokenizeRequestOptions(url, queryParameters, piiParamTokens, data) : undefined; + } + + /** @inheritdoc */ + public processFormData(data: any, type: string) { + return processFormData(data, type); + } + + /** @inheritdoc */ + public processCall(url: string, options: RequestOptions, _apiType: string | ApiTypes, _apiName: string, _revivers?: unknown, _operationId?: unknown): Promise { + const headers = { + type: 'application/json', + ...options.headers.entries() + }; + const blob = new Blob(options.body ? [JSON.stringify(options.body)] : [], headers); + const success = navigator.sendBeacon(url, blob); + if (!success) { + throw new Error(`Failed to send beacon to ${url}`); + } + + return Promise.resolve(); + } +} diff --git a/packages/@ama-sdk/client-beacon/src/index.spec.ts b/packages/@ama-sdk/client-beacon/src/index.spec.ts new file mode 100644 index 0000000000..f85b25ec7b --- /dev/null +++ b/packages/@ama-sdk/client-beacon/src/index.spec.ts @@ -0,0 +1,5 @@ +// TODO be removed as soon as we have one test in this package + +it('should be removed as soon as we have one test in this package', () => { + expect(true).toBe(true); +}); diff --git a/packages/@ama-sdk/client-beacon/src/public_api.ts b/packages/@ama-sdk/client-beacon/src/public_api.ts new file mode 100644 index 0000000000..1f6740bc94 --- /dev/null +++ b/packages/@ama-sdk/client-beacon/src/public_api.ts @@ -0,0 +1 @@ +export * from './api-beacon-client'; diff --git a/packages/@ama-sdk/client-beacon/testing/jest.config.ut.builders.js b/packages/@ama-sdk/client-beacon/testing/jest.config.ut.builders.js new file mode 100644 index 0000000000..ed52ef11a8 --- /dev/null +++ b/packages/@ama-sdk/client-beacon/testing/jest.config.ut.builders.js @@ -0,0 +1,15 @@ +const path = require('node:path'); +const getJestProjectConfig = require('../../../../jest.config.ut').getJestProjectConfig; +const rootDir = path.join(__dirname, '..'); + +/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ +module.exports = { + ...getJestProjectConfig(rootDir, false), + displayName: `${require('../package.json').name}/builders`, + rootDir, + testPathIgnorePatterns: [ + '/.*/templates/.*', + '/src/.*', + '\\.it\\.spec\\.ts$' + ] +}; diff --git a/packages/@ama-sdk/client-beacon/testing/jest.config.ut.js b/packages/@ama-sdk/client-beacon/testing/jest.config.ut.js new file mode 100644 index 0000000000..a977528613 --- /dev/null +++ b/packages/@ama-sdk/client-beacon/testing/jest.config.ut.js @@ -0,0 +1,21 @@ +const path = require('node:path'); +const getJestProjectConfig = require('../../../../jest.config.ut').getJestProjectConfig; +const rootDir = path.join(__dirname, '..'); + +/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ +module.exports = { + ...getJestProjectConfig(rootDir, false), + displayName: require('../package.json').name, + rootDir, + fakeTimers: { + enableGlobally: true, + // TODO re-enable fake dates when issue fixed https://github.com/sinonjs/fake-timers/issues/437 and v12 is used by Jest + doNotFake: ['Date'] + }, + testPathIgnorePatterns: [ + '/.*/templates/.*', + '/builders/.*', + '/schematics/.*', + '\\.it\\.spec\\.ts$' + ] +}; diff --git a/packages/@ama-sdk/client-beacon/testing/setup-jest.ts b/packages/@ama-sdk/client-beacon/testing/setup-jest.ts new file mode 100644 index 0000000000..42c586a2d8 --- /dev/null +++ b/packages/@ama-sdk/client-beacon/testing/setup-jest.ts @@ -0,0 +1,2 @@ +import '@o3r/test-helpers/setup-jest-builders'; + diff --git a/packages/@ama-sdk/client-beacon/tsconfig.build.json b/packages/@ama-sdk/client-beacon/tsconfig.build.json new file mode 100644 index 0000000000..e76c615d1c --- /dev/null +++ b/packages/@ama-sdk/client-beacon/tsconfig.build.json @@ -0,0 +1,27 @@ +{ + "extends": "../../../tsconfig.build", + "compilerOptions": { + "sourceMap": true, + "incremental": true, + "composite": true, + "rootDir": ".", + "lib": [ + "dom", + "dom.iterable", + "scripthost", + "es2017.object", + "esnext" + ], + "declarationMap": true, + "target": "es2020", + "module": "es2020", + "tsBuildInfoFile": "./build/tsconfig.tsbuildinfo", + "outDir": "./dist" + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "**/*.spec.ts" + ] +} diff --git a/packages/@ama-sdk/client-beacon/tsconfig.builders.json b/packages/@ama-sdk/client-beacon/tsconfig.builders.json new file mode 100644 index 0000000000..eb1c38fbee --- /dev/null +++ b/packages/@ama-sdk/client-beacon/tsconfig.builders.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.build", + "compilerOptions": { + "incremental": true, + "composite": true, + "outDir": "./dist", + "module": "CommonJS", + "rootDir": ".", + "tsBuildInfoFile": "build/.tsbuildinfo.builders" + }, + "include": [ + "schematics/**/*.ts" + ], + "exclude": [ + "**/*.spec.ts", + "schematics/**/templates/**", + "schematics/ng-add/mocks/**" + ] +} diff --git a/packages/@ama-sdk/client-beacon/tsconfig.eslint.json b/packages/@ama-sdk/client-beacon/tsconfig.eslint.json new file mode 100644 index 0000000000..38eec16702 --- /dev/null +++ b/packages/@ama-sdk/client-beacon/tsconfig.eslint.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig", + "include": [ + ".eslintrc.js", + "jest.config.js", + "testing/*", + "scripts/*" + ] +} diff --git a/packages/@ama-sdk/client-beacon/tsconfig.json b/packages/@ama-sdk/client-beacon/tsconfig.json new file mode 100644 index 0000000000..2a565f391f --- /dev/null +++ b/packages/@ama-sdk/client-beacon/tsconfig.json @@ -0,0 +1,12 @@ +/* IDE usage only */ +{ + "extends": "../../../tsconfig.base", + "references": [ + { + "path": "./tsconfig.spec.json" + }, + { + "path": "./tsconfig.build.json" + } + ] +} diff --git a/packages/@ama-sdk/client-beacon/tsconfig.spec.json b/packages/@ama-sdk/client-beacon/tsconfig.spec.json new file mode 100644 index 0000000000..cf096bfec9 --- /dev/null +++ b/packages/@ama-sdk/client-beacon/tsconfig.spec.json @@ -0,0 +1,25 @@ +{ + "extends": "../../../tsconfig.jest", + "compilerOptions": { + "esModuleInterop": true, + "strictNullChecks": false, + "strictPropertyInitialization":false, + "noImplicitAny": false, + "incremental": true, + "composite": true, + "declarationMap": false, + "strict": false, + "module": "ES2020", + "target": "ES2020" + }, + "include": [ + "./**/*.spec.ts" + ], + "exclude": [ + ], + "references": [ + { + "path": "./tsconfig.build.json" + } + ] +} diff --git a/packages/@ama-sdk/client-fetch/package.json b/packages/@ama-sdk/client-fetch/package.json index 459a1ef298..13e4b8cabb 100644 --- a/packages/@ama-sdk/client-fetch/package.json +++ b/packages/@ama-sdk/client-fetch/package.json @@ -4,7 +4,7 @@ "publishConfig": { "access": "public" }, - "description": "API Request client for @ama-sdk/core based SDK", + "description": "Fetch API Request client for @ama-sdk/core based SDK", "module": "dist/src/public_api.js", "esm2015": "dist/esm2015/public_api.js", "esm2020": "dist/src/public_api.js", diff --git a/packages/@ama-sdk/core/src/clients/api-beacon-client.ts b/packages/@ama-sdk/core/src/clients/api-beacon-client.ts index 6f76b3ded4..459a6cf9d7 100644 --- a/packages/@ama-sdk/core/src/clients/api-beacon-client.ts +++ b/packages/@ama-sdk/core/src/clients/api-beacon-client.ts @@ -5,13 +5,19 @@ import type { PartialExcept } from '../fwk/api.interface'; import type { ApiClient, RequestOptionsParameters } from '../fwk/core/api-client'; import type { BaseApiClientOptions } from '../fwk/core/base-api-constructor'; -/** @see BaseApiClientOptions */ +/** + * @see BaseApiClientOptions + * @deprecated Use the one exposed by {@link @ama-sdk/client-beacon}, will be removed in v13 + */ export interface BaseApiBeaconClientOptions extends BaseApiClientOptions { /** @inheritdoc */ replyPlugins: never[]; } -/** @see BaseApiConstructor */ +/** + * @see BaseApiConstructor + * @deprecated Use the one exposed by {@link @ama-sdk/client-beacon}, will be removed in v13 + */ export interface BaseApiBeaconClientConstructor extends PartialExcept, 'basePath'> { } @@ -23,6 +29,7 @@ const DEFAULT_OPTIONS: Omit = { /** * Determine if the given value is a promise + * @deprecated Use the one exposed by {@link @ama-sdk/client-beacon}, will be removed in v13 * @param value The value to test */ // NOTE: the `extends unknown` is required for ESM build with TSC diff --git a/packages/@ama-sdk/core/src/plugins/mock-intercept/mock-intercept.angular.ts b/packages/@ama-sdk/core/src/plugins/mock-intercept/mock-intercept.angular.ts index c615ee74f0..5be3cbd4c4 100644 --- a/packages/@ama-sdk/core/src/plugins/mock-intercept/mock-intercept.angular.ts +++ b/packages/@ama-sdk/core/src/plugins/mock-intercept/mock-intercept.angular.ts @@ -9,6 +9,7 @@ import { HttpResponse } from '@angular/common/http'; * * This plugin should be used only with the MockInterceptRequest Plugin. * It will allow the user to delay the response or to handle the getResponse function provided with the mock (if present). + * @deprecated Use the one exposed by {@link @ama-sdk/client-angular}, will be removed in v13 */ export class MockInterceptAngular implements AngularPlugin { diff --git a/yarn.lock b/yarn.lock index ebbc4c9fa1..8ddcbf6433 100644 --- a/yarn.lock +++ b/yarn.lock @@ -267,6 +267,151 @@ __metadata: languageName: node linkType: hard +"@ama-sdk/client-angular@workspace:packages/@ama-sdk/client-angular": + version: 0.0.0-use.local + resolution: "@ama-sdk/client-angular@workspace:packages/@ama-sdk/client-angular" + dependencies: + "@ama-sdk/core": "workspace:^" + "@angular-devkit/core": "npm:~18.2.0" + "@angular-devkit/schematics": "npm:~18.2.0" + "@angular-eslint/eslint-plugin": "npm:~18.3.0" + "@angular/common": "npm:~18.2.0" + "@angular/core": "npm:~18.2.0" + "@nx/eslint-plugin": "npm:~19.5.0" + "@nx/jest": "npm:~19.5.0" + "@o3r/build-helpers": "workspace:^" + "@o3r/eslint-plugin": "workspace:^" + "@o3r/test-helpers": "workspace:^" + "@schematics/angular": "npm:~18.2.0" + "@stylistic/eslint-plugin-ts": "npm:~2.4.0" + "@swc/cli": "npm:~0.4.0" + "@swc/core": "npm:~1.7.0" + "@swc/helpers": "npm:~0.5.0" + "@types/jest": "npm:~29.5.2" + "@types/node": "npm:^20.0.0" + "@types/uuid": "npm:^9.0.0" + "@typescript-eslint/eslint-plugin": "npm:^7.14.1" + "@typescript-eslint/parser": "npm:^7.14.1" + "@typescript-eslint/utils": "npm:^7.14.1" + cpy-cli: "npm:^5.0.0" + eslint: "npm:^8.57.0" + eslint-import-resolver-node: "npm:^0.3.9" + eslint-plugin-jest: "npm:~28.8.0" + eslint-plugin-jsdoc: "npm:~48.11.0" + eslint-plugin-prefer-arrow: "npm:~1.2.3" + eslint-plugin-unicorn: "npm:^54.0.0" + jest: "npm:~29.7.0" + jest-junit: "npm:~16.0.0" + jsonc-eslint-parser: "npm:~2.4.0" + minimist: "npm:^1.2.6" + pid-from-port: "npm:^1.1.3" + rimraf: "npm:^5.0.1" + rxjs: "npm:^7.8.1" + semver: "npm:^7.5.2" + ts-jest: "npm:~29.2.0" + ts-node: "npm:~10.9.2" + tslib: "npm:^2.6.2" + type-fest: "npm:^4.10.2" + typescript: "npm:~5.5.4" + uuid: "npm:^10.0.0" + zone.js: "npm:~0.14.2" + peerDependencies: + "@ama-sdk/core": "workspace:^" + "@angular-devkit/schematics": ~18.2.0 + "@angular/cli": ~18.2.0 + "@angular/common": ~18.2.0 + "@o3r/schematics": "workspace:^" + "@schematics/angular": ~18.2.0 + rxjs: ^7.8.1 + typescript: ~5.5.4 + peerDependenciesMeta: + "@angular-devkit/schematics": + optional: true + "@angular/cli": + optional: true + "@angular/common": + optional: true + "@o3r/schematics": + optional: true + "@schematics/angular": + optional: true + typescript: + optional: true + languageName: unknown + linkType: soft + +"@ama-sdk/client-beacon@workspace:packages/@ama-sdk/client-beacon": + version: 0.0.0-use.local + resolution: "@ama-sdk/client-beacon@workspace:packages/@ama-sdk/client-beacon" + dependencies: + "@ama-sdk/core": "workspace:^" + "@angular-devkit/core": "npm:~18.2.0" + "@angular-devkit/schematics": "npm:~18.2.0" + "@angular-eslint/eslint-plugin": "npm:~18.3.0" + "@angular/common": "npm:~18.2.0" + "@angular/core": "npm:~18.2.0" + "@nx/eslint-plugin": "npm:~19.5.0" + "@nx/jest": "npm:~19.5.0" + "@o3r/build-helpers": "workspace:^" + "@o3r/eslint-plugin": "workspace:^" + "@o3r/test-helpers": "workspace:^" + "@schematics/angular": "npm:~18.2.0" + "@stylistic/eslint-plugin-ts": "npm:~2.4.0" + "@swc/cli": "npm:~0.4.0" + "@swc/core": "npm:~1.7.0" + "@swc/helpers": "npm:~0.5.0" + "@types/jest": "npm:~29.5.2" + "@types/node": "npm:^20.0.0" + "@types/uuid": "npm:^9.0.0" + "@typescript-eslint/eslint-plugin": "npm:^7.14.1" + "@typescript-eslint/parser": "npm:^7.14.1" + "@typescript-eslint/utils": "npm:^7.14.1" + cpy-cli: "npm:^5.0.0" + eslint: "npm:^8.57.0" + eslint-import-resolver-node: "npm:^0.3.9" + eslint-plugin-jest: "npm:~28.8.0" + eslint-plugin-jsdoc: "npm:~48.11.0" + eslint-plugin-prefer-arrow: "npm:~1.2.3" + eslint-plugin-unicorn: "npm:^54.0.0" + jest: "npm:~29.7.0" + jest-junit: "npm:~16.0.0" + jsonc-eslint-parser: "npm:~2.4.0" + minimist: "npm:^1.2.6" + pid-from-port: "npm:^1.1.3" + rimraf: "npm:^5.0.1" + rxjs: "npm:^7.8.1" + semver: "npm:^7.5.2" + ts-jest: "npm:~29.2.0" + ts-node: "npm:~10.9.2" + tslib: "npm:^2.6.2" + type-fest: "npm:^4.10.2" + typescript: "npm:~5.5.4" + uuid: "npm:^10.0.0" + zone.js: "npm:~0.14.2" + peerDependencies: + "@ama-sdk/core": "workspace:^" + "@angular-devkit/schematics": ~18.2.0 + "@angular/cli": ~18.2.0 + "@angular/common": ~18.2.0 + "@o3r/schematics": "workspace:^" + "@schematics/angular": ~18.2.0 + typescript: ~5.5.4 + peerDependenciesMeta: + "@angular-devkit/schematics": + optional: true + "@angular/cli": + optional: true + "@angular/common": + optional: true + "@o3r/schematics": + optional: true + "@schematics/angular": + optional: true + typescript: + optional: true + languageName: unknown + linkType: soft + "@ama-sdk/client-fetch@workspace:^, @ama-sdk/client-fetch@workspace:packages/@ama-sdk/client-fetch": version: 0.0.0-use.local resolution: "@ama-sdk/client-fetch@workspace:packages/@ama-sdk/client-fetch"