diff --git a/.claude/new-method-structure.md b/.claude/new-method-structure.md new file mode 100644 index 00000000..8addaaf0 --- /dev/null +++ b/.claude/new-method-structure.md @@ -0,0 +1,289 @@ +Axios needs to be removed from dependencies entirly. Instead we want to build on nodes build-in fetch method. Assume that globally known types from fetch like Response and Requests are knwon and do not need any abstraction. With this, we do not need hal json anymore and will just use static URLs. We also want to redesign this sdk to modernize it. The current state is documented in the README.md. Be sure to check the Advanced topic. It turns out this part especialle is to complicated for users. So we want to keep the "simple" method structure of + +doSomething(context: DvelopContext, params: MethodParams): T {} + +but i want a better way for users to +- manipulate the requestparams (e.g. they need to dispable https-certification, longer timeouts, retries etc.) +- manipulate the raw json response + +Users of this sdk are typically consultants who don't even work with typescript since it's too complex. Make a plan for a new Structure for methods (while the simple signatures stay the same) and how to implement this new https method. Use factory-patterns where you see fit but do not enforce them. + +Context + + The SDK currently wraps axios in a complex factory-injection pattern (_getRepositoryFactory(httpFn, transformFn)) that was designed for testability and advanced extensibility. + Non-TypeScript consultant users find it impossible to use — they just want to set a timeout or see the raw response, but doing so requires understanding TypeScript generics, + factory functions, and the axios config shape. + + Additionally: + - DvelopHttpRequestConfig extends AxiosRequestConfig leaks the axios API surface + - HAL-JSON following (follows: ["repo", "dmsobjectwithmapping"]) adds hidden network hops and complexity + - Static URLs are known and stable; HAL discovery is unnecessary overhead + - Every method call creates a new DvelopHttpClient via defaultDvelopHttpClientFactory() (wasteful) + + Goal: Keep doSomething(context, params) for simple use, but replace the factory extension point with a plain options third parameter that any JS user can understand. + + --- + New Method Signature + + All SDK methods gain an optional third parameter: + + doSomething(context: DvelopContext, params: MethodParams, options?: DvelopRequestOptions): Promise + + This is backward compatible — existing code that omits options keeps working unchanged. + + --- + DvelopRequestOptions (exported from @dvelop-sdk/core) + + export interface DvelopRequestOptions { + /** Replace the built-in fetch. Use for custom TLS, retries, proxies. */ + fetch?: typeof globalThis.fetch; + + /** Extra headers merged on top of the SDK defaults. */ + headers?: Record; + + /** Cancellation / timeout signal. Example: AbortSignal.timeout(10_000) */ + signal?: AbortSignal; + + /** + * Called with a clone of the raw Response before the default JSON transform. + * Return a value to replace the method's return. Return undefined (or nothing) + * to let the default transform run. + */ + onResponse?: (response: Response) => unknown | Promise; + } + + Usage examples (plain JavaScript) + + // Timeout + const repo = await getRepository(context, params, { + signal: AbortSignal.timeout(5000) + }); + // Disable TLS (Node 18+ via custom undici dispatcher) + import { Agent } from "undici"; + const insecureFetch = (url, init) => + fetch(url, { ...init, dispatcher: new Agent({ connect: { rejectUnauthorized: false } }) }); + + const repo = await getRepository(context, params, { fetch: insecureFetch }); + + // Retry wrapper + import { fetchWithRetry } from "fetch-retry"; // any retry lib + const repo = await getRepository(context, params, { fetch: fetchWithRetry(fetch) }); + + // Access raw JSON response + const raw = await getRepository(context, params, { + onResponse: async (response) => response.json() // bypasses default transform + }); + + // Log a header without replacing the result + const repo = await getRepository(context, params, { + onResponse: (response) => { + console.log("ETag:", response.headers.get("etag")); + // returns undefined → default transform still runs + } + }); + + --- + @dvelop-sdk/core changes + + New dvelopFetch function (packages/core/src/http/fetch.ts) + + export async function dvelopFetch( + context: DvelopContext, + path: string, + init: RequestInit, + options?: DvelopRequestOptions + ): Promise { + const headers = new Headers(init.headers); + headers.set("Accept", "application/json"); + if (context.authSessionId) headers.set("Authorization", `Bearer ${context.authSessionId}`); + headers.set(DVELOP_REQUEST_ID_HEADER, context.requestId ?? generateRequestId()); + if (context.traceContext) headers.set(TRACEPARENT_HEADER, buildTraceparentHeader(context.traceContext)); + if (options?.headers) { + for (const [k, v] of Object.entries(options.headers)) headers.set(k, v); + } + + const fetchFn = options?.fetch ?? globalThis.fetch; + return fetchFn(`${context.systemBaseUri}${path}`, { + ...init, + headers, + signal: options?.signal ?? init.signal, + }); + } + + dvelopFetch returns the raw Response — it does not throw on non-2xx. Error mapping is per-package. + + Optional helper: withOptions + + export function withOptions( + fn: (ctx: DvelopContext, params: P, opts?: DvelopRequestOptions) => Promise, + defaults: DvelopRequestOptions + ): (ctx: DvelopContext, params: P, opts?: DvelopRequestOptions) => Promise { + return (ctx, params, opts) => fn(ctx, params, { ...defaults, ...opts }); + } + + Lets users create pre-configured versions without TypeScript knowledge: + const getRepoWithRetry = withOptions(getRepository, { fetch: myRetryFetch }); + + Remove from core + + - packages/core/src/http/http-client.ts (DvelopHttpClient, axiosHttpClientFactory, etc.) + - packages/core/src/http/axios-follow-hal-json.ts + - packages/core/src/util/deep-merge-objects.ts (no longer needed) + - axios and lodash.merge from packages/core/package.json + + Update core exports + + Export DvelopRequestOptions, dvelopFetch, withOptions. Remove all axios-related exports. + + --- + Per-package utils/http.ts pattern + + Each package replaces its complex factory+error-mapping with a simple fetch wrapper: + + // packages/dms/src/utils/http.ts + import { dvelopFetch, DvelopRequestOptions, DvelopContext, BadInputError, ... } from "@dvelop-sdk/core"; + + export async function dmsRequest( + context: DvelopContext, + path: string, + init: RequestInit, + options?: DvelopRequestOptions + ): Promise { + let response: Response; + try { + response = await dvelopFetch(context, path, init, options); + } catch (e: any) { + throw new DmsError(`Request to DMS-App failed: ${e.message}`, e); + } + + if (!response.ok) { + let body = ""; + try { body = await response.text(); } catch { /* ignore */ } + let parsed: any = {}; + try { parsed = JSON.parse(body); } catch { /* ignore */ } + + switch (response.status) { + case 400: throw new BadInputError(parsed.reason ?? "Bad request"); + case 401: throw new UnauthorizedError(parsed.reason ?? "Unauthorized"); + case 403: throw new ForbiddenError(parsed.reason ?? "Forbidden"); + case 404: throw new NotFoundError(parsed.reason ?? "Not found"); + default: throw new DmsError(`DMS-App responded with ${response.status}`); + } + } + + return response; + } + + Same pattern for task, identityprovider, business-objects utils — each mapping its own error classes and status codes (e.g. task has a 429 case). + + --- + Method implementation pattern + + // packages/dms/src/repositories/get-repository/get-repository.ts + export async function getRepository( + context: DvelopContext, + params: GetRepositoryParams, + options?: DvelopRequestOptions + ): Promise { + const response = await dmsRequest( + context, + `/dms/r/${params.repositoryId}`, + { method: "GET" }, + options + ); + + if (options?.onResponse) { + const result = await options.onResponse(response.clone()); + if (result !== undefined) return result as Repository; + } + + const data = await response.json(); + return { + repositoryId: data.id, + name: data.name, + sourceId: data._links?.source?.href ?? "", + }; + } + + Key points: + - No factory, no transform function injection + - Static URL replaces HAL following + - response.clone() is passed to onResponse so the original body is still available for default transform + - The _xFactory and _xDefaultTransformFunction internal exports are removed + + --- + Static URL mapping + + DMS uses HAL following to discover URLs. The static equivalents (to be confirmed/discovered per-method during implementation): + + ┌────────────────────────────────────┬──────────────────────────────────┐ + │ Old (HAL follows) │ New static path │ + ├────────────────────────────────────┼──────────────────────────────────┤ + │ /dms → repo │ /dms/r/{repositoryId} │ + ├────────────────────────────────────┼──────────────────────────────────┤ + │ /dms → repos │ /dms/r │ + ├────────────────────────────────────┼──────────────────────────────────┤ + │ /dms/r/{id} → dmsobjectwithmapping │ /dms/r/{repositoryId}/o2m │ + ├────────────────────────────────────┼──────────────────────────────────┤ + │ /dms/r/{id} → chunkedupload │ /dms/r/{repositoryId}/blob (TBC) │ + ├────────────────────────────────────┼──────────────────────────────────┤ + │ /dms → repo → dmsobjectsbyids │ /dms/r/{repositoryId}/objs (TBC) │ + └────────────────────────────────────┴──────────────────────────────────┘ + + ▎ Note: Static paths for DMS will be provided by the user before implementation. The task package already uses static paths (/task/tasks) and needs no URL changes. + + --- + Test strategy + + Old tests mocked httpRequestFunction (the factory injection point). New tests inject fetch via options: + + // Old + const fn = _getRepositoryFactory(mockHttpRequestFunction, _getRepositoryDefaultTransformFunction); + await fn(context, params); + expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { method: "GET", url: "/dms", follows: [...] }); + + // New + const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify(mockData), { status: 200 })); + const result = await getRepository(context, params, { fetch: mockFetch }); + expect(mockFetch).toHaveBeenCalledWith( + "https://example.d-velop.cloud/dms/r/repo123", + expect.objectContaining({ method: "GET" }) + ); + + Tests for onResponse: + it("should use onResponse return value when provided", async () => { + const mockFetch = jest.fn().mockResolvedValue(new Response("{}")); + const custom = { customField: true }; + const result = await getRepository(context, params, { + fetch: mockFetch, + onResponse: async () => custom, + }); + expect(result).toBe(custom); + }); + + --- + Files to change + + Core package (packages/core/): + - src/http/http-client.ts → replace with src/http/fetch.ts (new dvelopFetch) + - src/http/axios-follow-hal-json.ts → delete + - src/util/deep-merge-objects.ts → delete + - src/index.ts → update exports + - package.json → remove axios, lodash.merge + + Per-package (repeat for dms, task, identityprovider, business-objects): + - src/utils/http.ts → replace with simpler pkgRequest wrapper + - src/utils/http.spec.ts → update tests + - Every method .ts file → remove factory + transform function, add options? param, use static URL + - Every method .spec.ts file → update mocking pattern + + Scope note: This touches ~40 method files across 4 packages. The pattern is identical for all — one example implementation unblocks the rest. A good first candidate is + packages/task/ (already uses static URLs, simpler than DMS). + + --- + Verification + + 1. npm run build across all packages — no TypeScript errors + 2. npm test — all existing tests pass with updated mocks + 3. Manual smoke test: run a real getRepository call against a test tenant with options.signal = AbortSignal.timeout(1) → should throw AbortError + 4. Verify that plain JavaScript usage (no TypeScript) works without type errors leaking \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index e1a113a5..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "extends": [ - "eslint:recommended", - "plugin:jest/recommended" - ], - "globals": { - "Console": true - }, - "env": { - "node": true, - "browser": true, - "jest/globals": true, - "es6": true - }, - "parser": "@typescript-eslint/parser", - "plugins": [ - "@typescript-eslint", - "jest" - ], - "ignorePatterns": [ - "tmp-*.ts" - ], - "rules": { - "semi": [ - "error", - "always" - ], - "quotes": [ - "error", - "double" - ], - "indent": [ - "error", - 2 - ], - "function-call-argument-newline": [ - "error", - "never" - ], - "function-paren-newline": [ - "error", - "multiline" - ], - "no-console": [ - "error" - ], - "no-var": "error", - "no-redeclare": "off", - "@typescript-eslint/no-redeclare": [ - "error" - ], - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": [ - "error", - { - "argsIgnorePattern": "^_" - } - ], - "@typescript-eslint/adjacent-overload-signatures": [ - "error" - ] - } -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5ccd8c74..c67f94e0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ tmp-* chicken.* .vscode .idea +e2e.* +.claude \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 420f0d06..1d9393e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -218,8 +218,133 @@ As mentioned we are using [typedoc](https://typedoc.org/) to document code. A do ... ``` -### Commit clean code -Ideally your commits should include a single valuable contribution that is tested, linted and documented. On every commit ```npm test``` and ```npm lint``` is run by default and your commit gets aborted if one of these fails. Be aware that these commands run against the *real* current state of the project, not the committed one. +### Git +This repository uses Git as VSC tool for code. Github is used as the remote repository. + +#### What to commit + +A commit should contain a _"logical unit of change"_. + +> You should make new commits often, based around logical units of change. Over time, commits should tell a story of the history of your repository and how it came to be the way that it currently is. + + + +Additionally the code should be *complete* after each commit, meaning that the changes made compile with all guidelines formulated and pass the CI (launchable, tested, linted, formatted, etc.) + +A cool practical approach was formulated in by Jason McCreary: +> I make a commit when: +> 1. I complete a unit of work. +> 2. I have changes I may want to undo. + + + +#### Commit messages + +Commit messages should be structured according to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) + +```text +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +- **types** + + Currently the following types are predominantly used: + + | type | used for | + |----------|----------| + | feat | Feature (or milestone for a feature) | + | fix | Bugfix | + | ci | Changes to CI or project behavior. For now this includes types `build`, `test` and `docs`. | + | refactor | Code changes without any changes to behavior (except performance). For not this includes type `perf` and `style` | + | revert | Revert a commit | + +- **scope** + + Scopes are optional and may be used where they appear appropriate. Remember that this might be redundant with the branch-name as branches get merged and deleted. + +- **description** + + A short summary of the code changes. + - use the imperative, present tense: "change" not "changed" nor "changes" + - don't capitalize the first letter + - don't append dot `.` + +- **body** + + A longer commit body may be provided after the short description, providing additional contextual information about the code changes (e.g. enumeration of changes or contrast with previous behavior). + - use the imperative, present tense: "change" not "changed" nor "changes" + - capitalize the first letter + - may append dot `.` + +- **footer(s)** + + Footers may be added in the form of key-value-pairs. They may be used for + + - [Github Keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-request) + + `Closes #42` + + - References to commit SHAs + + `Ref: a215868` + + - Breaking Changes + + `BREAKING CHANGE: ` + + +- **examples** + + ``` + feat: add salt to noodle water + ``` + + ``` + fix: exhaust hood turns of randomly + + Change exhaust hood behavior to not depend on water temperature + ``` + + ``` + feat(carbonara): add pancetta as final ingredient + + Add pancetta (italian bacon) to recipe: + - is cut into cubes + - is cooked (although is commonly eaten raw) + - expected to be seasoned with rosemary and sage + + BREAKING CHANGE: the dish is no longer vegetarian + ``` + + ``` + revert: commit introduces bug where pasta is not al dente anymore + + Ref: a215868 + ``` + + +#### Where to commit + +This repo uses part of the [Git flow branching strategy](https://www.gitkraken.com/learn/git/git-flow). + +| branch-name | details | commit directly | merge into +|---------------|------------------------------------|-----|--------------------| +| `main` | most recent release | no | - | +| `development` | next release | yes | `main` | +| `feature/*` | a feature currently in development | yes | `development` | +| `hotfix/*` | a bug currently fixed | yes | `main` & `development` | + +We consciously omit +- `release/*` +- `support/*` ## Pull Request diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..34593355 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,45 @@ +import js from "@eslint/js"; +import tsPlugin from "@typescript-eslint/eslint-plugin"; +import tsParser from "@typescript-eslint/parser"; +import jestPlugin from "eslint-plugin-jest"; +import globals from "globals"; + +export default [ + { + ignores: ["**/lib/**", "**/tmp-*.ts"], + }, + js.configs.recommended, + { + files: ["packages/**/*.ts"], + plugins: { + "@typescript-eslint": tsPlugin, + }, + languageOptions: { + parser: tsParser, + globals: { + ...globals.node, + ...globals.browser, + Console: "readonly", + }, + }, + rules: { + "semi": ["error", "always"], + "quotes": ["error", "double"], + "indent": ["error", 2], + "function-call-argument-newline": ["error", "never"], + "function-paren-newline": ["error", "multiline"], + "no-console": ["error"], + "no-var": "error", + "no-redeclare": "off", + "@typescript-eslint/no-redeclare": ["error"], + "no-undef": "off", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "@typescript-eslint/adjacent-overload-signatures": ["error"], + }, + }, + { + ...jestPlugin.configs["flat/recommended"], + files: ["packages/**/*.spec.ts"], + }, +]; diff --git a/jest.json b/jest.json index d5f73ac6..6495a874 100644 --- a/jest.json +++ b/jest.json @@ -11,6 +11,10 @@ "transformIgnorePatterns": [ "/node_modules/(?!(axios)/)" ], + "moduleNameMapper": { + "^(.*)\\.js$": "$1" + }, + "testPathIgnorePatterns": ["/node_modules/", "e2e\\."], "testEnvironment": "node", "collectCoverage": true, "collectCoverageFrom": [ diff --git a/package-lock.json b/package-lock.json index 2562052f..3cca97c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,18 +17,20 @@ "packages/logging" ], "devDependencies": { - "@types/jest": "^27.0.2", - "@types/node": "^14.14.41", - "@typescript-eslint/eslint-plugin": "^4.16.1", - "@typescript-eslint/parser": "^4.16.1", - "eslint": "^7.21.0", - "eslint-plugin-jest": "^24.1.9", - "jest": "^27.5.1", + "@eslint/js": "^9.0.0", + "@types/jest": "^29.0.0", + "@types/node": "^18.0.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "eslint": "^9.0.0", + "eslint-plugin-jest": "^29.0.0", + "globals": "^16.0.0", + "jest": "^29.0.0", "license-checker": "^25.0.1", - "ts-jest": "^27.0.5", + "ts-jest": "^29.0.0", "ts-node": "^10.9.2", - "typedoc": "^0.22.13", - "typescript": "^4.1.2" + "typedoc": "^0.28.0", + "typescript": "^5.0.0" } }, "node_modules/@babel/code-frame": { @@ -42,9 +44,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -52,21 +54,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -83,13 +85,13 @@ } }, "node_modules/@babel/core/node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -97,13 +99,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -115,14 +110,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -132,14 +127,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -159,9 +154,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -169,29 +164,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -201,9 +196,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -211,9 +206,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -221,9 +216,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -231,9 +226,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -241,14 +236,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -349,13 +344,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -420,13 +415,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -461,6 +456,22 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", @@ -572,13 +583,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -588,28 +599,28 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -618,18 +629,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -637,13 +648,13 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -652,14 +663,14 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -728,60 +739,259 @@ "resolved": "packages/task", "link": true }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/@gerrit0/mini-shiki": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz", + "integrity": "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==", "dev": true, "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.23.0", + "@shikijs/langs": "^3.23.0", + "@shikijs/themes": "^3.23.0", + "@shikijs/types": "^3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { - "node": ">= 4" + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -811,9 +1021,9 @@ } }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", "engines": { @@ -821,61 +1031,61 @@ } }, "node_modules/@jest/console": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", - "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/core": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", - "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^27.5.1", - "@jest/reporters": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "emittery": "^0.8.1", + "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^27.5.1", - "jest-config": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-resolve-dependencies": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "jest-watcher": "^27.5.1", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "rimraf": "^3.0.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -887,89 +1097,116 @@ } }, "node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^27.5.1" + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", - "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/types": "^27.5.1", - "expect": "^27.5.1" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", - "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", - "glob": "^7.1.2", + "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-haste-map": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", - "source-map": "^0.6.0", "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.1.0" + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -980,95 +1217,109 @@ } } }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jest/source-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", - "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "license": "MIT", "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", - "graceful-fs": "^4.2.9", - "source-map": "^0.6.0" + "graceful-fs": "^4.2.9" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-result": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", - "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/test-sequencer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", - "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^27.5.1", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-runtime": "^27.5.1" + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/transform": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", - "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.5.1", + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-util": "^27.5.1", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" + "write-file-atomic": "^4.0.2" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "license": "MIT", "dependencies": { + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^16.0.0", + "@types/yargs": "^17.0.8", "chalk": "^4.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jridgewell/gen-mapping": { @@ -1121,48 +1372,66 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", + "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@shikijs/langs": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", + "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "@shikijs/types": "3.23.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@shikijs/themes": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", + "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" } }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -1170,23 +1439,13 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" + "@sinonjs/commons": "^3.0.0" } }, "node_modules/@tsconfig/node10": { @@ -1281,6 +1540,13 @@ "@types/node": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/express": { "version": "4.17.25", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", @@ -1315,6 +1581,16 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -1349,14 +1625,14 @@ } }, "node_modules/@types/jest": { - "version": "27.5.2", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", - "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-matcher-utils": "^27.0.0", - "pretty-format": "^27.0.0" + "expect": "^29.0.0", + "pretty-format": "^29.0.0" } }, "node_modules/@types/json-schema": { @@ -1390,17 +1666,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "14.18.63", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", - "license": "MIT" - }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true, - "license": "MIT" + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/qs": { "version": "6.14.0", @@ -1451,17 +1723,17 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "dev": true, "license": "MIT" }, "node_modules/@types/yargs": { - "version": "16.0.11", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.11.tgz", - "integrity": "sha512-sbtvk8wDN+JvEdabmZExoW/HNr1cB7D/j4LT08rMiuikfA7m/JNJg7ATQcgzs34zHnoScDkY0ZRSl29Fkmk36g==", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, "license": "MIT", "dependencies": { @@ -1476,176 +1748,286 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", - "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", + "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/type-utils": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^4.0.0", - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.60.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", - "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", + "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", "dev": true, "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", - "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", + "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" + "@typescript-eslint/tsconfig-utils": "^8.60.1", + "@typescript-eslint/types": "^8.60.1", + "debug": "^4.4.3" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", - "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", + "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0" + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1" }, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", + "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", "dev": true, "license": "MIT", "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", - "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", + "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", + "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", + "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.1", + "@typescript-eslint/tsconfig-utils": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", + "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", - "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", + "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "4.33.0", - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "8.60.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, "node_modules/abbrev": { "version": "1.1.1", @@ -1655,9 +2037,9 @@ "license": "ISC" }, "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -1667,17 +2049,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -1688,33 +2059,10 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1728,16 +2076,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1834,16 +2172,6 @@ "node": ">=0.10.0" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -1851,67 +2179,23 @@ "dev": true, "license": "MIT" }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.31.1.tgz", - "integrity": "sha512-Ef8DUZSZQP6igY48mjGaoEjwhely97lserep0IFJifBH4YdKvwH5eMLniy3kig2HQoBNR8EkZpDjowxwTJcmbg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.4", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/babel-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", - "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.5.1", + "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" @@ -1934,20 +2218,47 @@ "node": ">=8" } }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/babel-plugin-jest-hoist": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", - "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", + "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -1978,17 +2289,17 @@ } }, "node_modules/babel-preset-jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", - "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^27.5.1", + "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" @@ -2002,13 +2313,16 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.17", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.17.tgz", - "integrity": "sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==", + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/brace-expansion": { @@ -2035,17 +2349,10 @@ "node": ">=8" } }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -2063,11 +2370,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -2106,19 +2413,6 @@ "dev": true, "license": "MIT" }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2140,9 +2434,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", "dev": true, "funding": [ { @@ -2211,15 +2505,18 @@ "license": "MIT" }, "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/co": { @@ -2260,18 +2557,6 @@ "dev": true, "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2280,12 +2565,34 @@ "license": "MIT" }, "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2308,48 +2615,6 @@ "node": ">= 8" } }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true, - "license": "MIT" - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2379,19 +2644,20 @@ "node": "*" } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" - }, "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } }, "node_modules/deep-is": { "version": "0.1.4", @@ -2410,15 +2676,6 @@ "node": ">=0.10.0" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2451,118 +2708,53 @@ } }, "node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "license": "MIT", "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/electron-to-chromium": { + "version": "1.5.366", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.366.tgz", + "integrity": "sha512-OlRuhb688YTCzzU3gXPLn6nGyd+F+53INE1qaKKlu6kETErE8FYsyDh0XqXEU+uBRn0MpCzz2vfNwORhkap8qg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "license": "MIT", - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.277", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.277.tgz", - "integrity": "sha512-wKXFZw4erWmmOz5N/grBoJ2XrNJGDFMu2+W5ACHza5rHtvsqrK4gb6rnLC7XxKB9WlJ+RmyQatuEXmtm86xbnw==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/error-ex": { @@ -2575,51 +2767,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2643,221 +2790,217 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-plugin-jest": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz", - "integrity": "sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA==", + "version": "29.15.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.2.tgz", + "integrity": "sha512-kEN4r9RZl1xcsb4arGq89LrcVdOUFII/JSCwtTPJyv16mDwmPrcuEQwpxqZHeINvcsd7oK5O/rhdGlxFRaZwvQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/experimental-utils": "^4.0.1" + "@typescript-eslint/utils": "^8.0.0" }, "engines": { - "node": ">=10" + "node": "^20.12.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": ">= 4", - "eslint": ">=5" + "@typescript-eslint/eslint-plugin": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "jest": "*", + "typescript": ">=4.8.4 <7.0.0" }, "peerDependenciesMeta": { "@typescript-eslint/eslint-plugin": { "optional": true + }, + "jest": { + "optional": true + }, + "typescript": { + "optional": true } } }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">= 4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -2887,16 +3030,6 @@ "node": ">=0.10" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -2910,7 +3043,7 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { + "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", @@ -2920,16 +3053,6 @@ "node": ">=4.0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2974,19 +3097,20 @@ } }, "node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/fast-deep-equal": { @@ -2996,23 +3120,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3027,33 +3134,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -3065,16 +3145,16 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -3105,18 +3185,17 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { @@ -3126,43 +3205,6 @@ "dev": true, "license": "ISC" }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", - "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3189,18 +3231,12 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true, - "license": "MIT" - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3221,30 +3257,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -3255,19 +3267,6 @@ "node": ">=8.0.0" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -3304,74 +3303,60 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" }, - "engines": { - "node": ">=10" + "bin": { + "handlebars": "bin/handlebars" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=0.4.7" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3382,37 +3367,11 @@ "node": ">=8" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3428,19 +3387,6 @@ "dev": true, "license": "ISC" }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3448,35 +3394,6 @@ "dev": true, "license": "MIT" }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3487,19 +3404,6 @@ "node": ">=10.17.0" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3652,13 +3556,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3672,13 +3569,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3697,30 +3587,20 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" + "semver": "^7.5.4" }, "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node": ">=10" } }, "node_modules/istanbul-lib-report": { @@ -3768,21 +3648,22 @@ } }, "node_modules/jest": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", - "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^27.5.1", + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^27.5.1" + "jest-cli": "^29.7.0" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -3794,76 +3675,76 @@ } }, "node_modules/jest-changed-files": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", - "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", "execa": "^5.0.0", - "throat": "^6.0.1" + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-circus": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", - "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.5.1", + "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" + "stack-utils": "^2.0.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-cli": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", - "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", + "create-jest": "^29.7.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", - "prompts": "^2.0.1", - "yargs": "^16.2.0" + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -3875,257 +3756,210 @@ } }, "node_modules/jest-config": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", - "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.8.0", - "@jest/test-sequencer": "^27.5.1", - "@jest/types": "^27.5.1", - "babel-jest": "^27.5.1", + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", - "glob": "^7.1.1", + "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-jasmine2": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runner": "^27.5.1", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^27.5.1", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { + "@types/node": "*", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, "ts-node": { "optional": true } } }, "node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", - "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "license": "MIT", "dependencies": { "detect-newline": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-each": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", - "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1" + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", - "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1", - "jsdom": "^16.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", - "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "license": "MIT", "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", - "walker": "^1.0.7" + "walker": "^1.0.8" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, - "node_modules/jest-jasmine2": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", - "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.5.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", - "pretty-format": "^27.5.1", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, "node_modules/jest-leak-detector": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", - "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util/node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -4134,17 +3968,18 @@ } }, "node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-pnp-resolver": { @@ -4166,175 +4001,157 @@ } }, "node_modules/jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "license": "MIT", "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", - "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.5.1", - "jest-validate": "^27.5.1", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", + "resolve.exports": "^2.0.0", "slash": "^3.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", - "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-snapshot": "^27.5.1" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", - "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^27.5.1", - "@jest/environment": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "emittery": "^0.8.1", + "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^27.5.1", - "jest-environment-jsdom": "^27.5.1", - "jest-environment-node": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-leak-detector": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-runtime": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runtime": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", - "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/globals": "^27.5.1", - "@jest/source-map": "^27.5.1", - "@jest/test-result": "^27.5.1", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-regex-util": "^27.5.1", - "jest-resolve": "^27.5.1", - "jest-snapshot": "^27.5.1", - "jest-util": "^27.5.1", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-snapshot": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", - "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.7.2", + "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^27.5.1", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-haste-map": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1", - "jest-util": "^27.5.1", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "natural-compare": "^1.4.0", - "pretty-format": "^27.5.1", - "semver": "^7.3.2" + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -4342,25 +4159,25 @@ "picomatch": "^2.2.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-validate": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", - "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1", + "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^27.5.1", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^27.5.1" + "pretty-format": "^29.7.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-validate/node_modules/camelcase": { @@ -4377,37 +4194,39 @@ } }, "node_modules/jest-watcher": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", - "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^27.5.1", - "@jest/types": "^27.5.1", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^27.5.1", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", "string-length": "^4.0.1" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, "engines": { - "node": ">= 10.13.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -4447,66 +4266,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -4561,13 +4320,6 @@ "node": ">=6" } }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4739,6 +4491,26 @@ "dev": true, "license": "MIT" }, + "node_modules/linkify-it": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", + "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -4752,13 +4524,6 @@ "node": ">=8" } }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -4772,13 +4537,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4826,30 +4584,50 @@ "dev": true, "license": "BSD-3-Clause", "dependencies": { - "tmpl": "1.0.5" + "tmpl": "1.0.5" + } + }, + "node_modules/markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.1", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } + "license": "Python-2.0" }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" }, "node_modules/merge-stream": { "version": "2.0.0", @@ -4858,16 +4636,6 @@ "dev": true, "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4882,27 +4650,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -4963,6 +4710,13 @@ "dev": true, "license": "MIT" }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4971,11 +4725,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/nopt": { "version": "4.0.3", @@ -5044,13 +4801,6 @@ "node": ">=8" } }, - "node_modules/nwsapi": { - "version": "2.2.23", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", - "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5128,16 +4878,16 @@ } }, "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5156,6 +4906,22 @@ "node": ">=8" } }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -5198,13 +4964,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true, - "license": "MIT" - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5242,16 +5001,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5306,18 +5055,18 @@ } }, "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -5333,16 +5082,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -5357,25 +5096,6 @@ "node": ">= 6" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5386,38 +5106,37 @@ "node": ">=6" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "opencollective", + "url": "https://opencollective.com/fast-check" } ], "license": "MIT" }, "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, "license": "MIT" }, @@ -5478,19 +5197,6 @@ "once": "^1.3.0" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5501,23 +5207,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -5573,83 +5262,11 @@ } }, "node_modules/resolve.exports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", - "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, "engines": { "node": ">=10" } @@ -5690,18 +5307,6 @@ "node": ">=8" } }, - "node_modules/shiki": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", - "integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsonc-parser": "^3.0.0", - "vscode-oniguruma": "^1.6.1", - "vscode-textmate": "5.2.0" - } - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -5726,24 +5331,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", @@ -5765,9 +5352,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "license": "MIT", "dependencies": { @@ -5960,20 +5547,6 @@ "node": ">=8" } }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -5987,99 +5560,68 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/table": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", - "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" }, "engines": { - "node": ">=10.0.0" + "node": ">=8" } }, - "node_modules/table/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, "engines": { - "node": ">=8" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": ">=8" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/throat": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", - "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/tmpl": { "version": "1.0.5", @@ -6101,35 +5643,6 @@ "node": ">=8.0" } }, - "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/treeify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", @@ -6140,40 +5653,59 @@ "node": ">=0.6" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-jest": { - "version": "27.1.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.5.tgz", - "integrity": "sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==", + "version": "29.4.11", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz", + "integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==", "dev": true, "license": "MIT", "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^27.0.0", - "json5": "2.x", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "20.x" + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.8.0", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" }, "bin": { "ts-jest": "cli.js" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", - "@types/jest": "^27.0.0", - "babel-jest": ">=27.0.0 <28", - "jest": "^27.0.0", - "typescript": ">=3.8 <5.0" + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" }, "peerDependenciesMeta": { "@babel/core": { "optional": true }, - "@types/jest": { + "@jest/transform": { + "optional": true + }, + "@jest/types": { "optional": true }, "babel-jest": { @@ -6181,9 +5713,38 @@ }, "esbuild": { "optional": true + }, + "jest-util": { + "optional": true } } }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -6228,19 +5789,6 @@ } } }, - "node_modules/ts-node/node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ts-node/node_modules/acorn-walk": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", @@ -6254,29 +5802,6 @@ "node": ">=0.4.0" } }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6300,100 +5825,73 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/typedoc": { - "version": "0.22.18", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.18.tgz", - "integrity": "sha512-NK9RlLhRUGMvc6Rw5USEYgT4DVAUFk7IF7Q6MYfpJ88KnTZP7EneEa4RcP+tX1auAcz7QT1Iy0bUSZBYYHdoyA==", + "version": "0.28.19", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.19.tgz", + "integrity": "sha512-wKh+lhdmMFivMlc6vRRcMGXeGEHGU2g8a2CkPTJjJlwRf1iXbimWIPcFolCqe4E0d/FRtGszpIrsp3WLpDB8Pw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "glob": "^8.0.3", + "@gerrit0/mini-shiki": "^3.23.0", "lunr": "^2.3.9", - "marked": "^4.0.16", - "minimatch": "^5.1.0", - "shiki": "^0.10.1" + "markdown-it": "^14.1.1", + "minimatch": "^10.2.5", + "yaml": "^2.8.3" }, "bin": { "typedoc": "bin/typedoc" }, "engines": { - "node": ">= 12.10.0" + "node": ">= 18", + "pnpm": ">= 10" }, "peerDependencies": { - "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x || 4.7.x" + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x" } }, - "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "node_modules/typedoc/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/typedoc/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "18 || 20 || >=22" } }, "node_modules/typedoc/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=10" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6401,19 +5899,36 @@ "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, "engines": { - "node": ">= 4.0.0" + "node": ">=0.8.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -6455,17 +5970,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-extend": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", @@ -6473,22 +5977,6 @@ "dev": true, "license": "MIT" }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", - "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", - "dev": true, - "license": "MIT" - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -6497,30 +5985,20 @@ "license": "MIT" }, "node_modules/v8-to-istanbul": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", - "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -6532,44 +6010,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", - "dev": true, - "license": "MIT" - }, - "node_modules/vscode-textmate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz", - "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "dev": true, - "license": "MIT", - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -6580,49 +6020,6 @@ "makeerror": "1.0.12" } }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=10.4" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6649,6 +6046,13 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -6675,54 +6079,19 @@ "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "signal-exit": "^3.0.7" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -6740,33 +6109,49 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yn": { @@ -6779,77 +6164,87 @@ "node": ">=6" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/app-router": { "name": "@dvelop-sdk/app-router", - "version": "3.2.9", + "version": "3.3.0", "license": "Apache-2.0", "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } }, "packages/business-objects": { "name": "@dvelop-sdk/business-objects", - "version": "2.0.5", + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } }, "packages/core": { "name": "@dvelop-sdk/core", - "version": "2.2.3", + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "axios": "^0.31.1", - "lodash.merge": "^4.6.2", - "uuid": "^8.3.2" + "lodash.merge": "^4.6.2" }, "devDependencies": { - "@types/lodash.merge": "^4.6.7", - "@types/uuid": "^8.3.1" + "@types/lodash.merge": "^4.6.7" } }, "packages/dms": { "name": "@dvelop-sdk/dms", - "version": "1.5.11", + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } }, "packages/express-utils": { "name": "@dvelop-sdk/express-utils", - "version": "1.2.5", + "version": "1.3.0", "license": "Apache-2.0", "dependencies": { - "@dvelop-sdk/app-router": "^3.2.2", - "@dvelop-sdk/core": "^2.2.3", - "@dvelop-sdk/identityprovider": "^4.0.8", + "@dvelop-sdk/app-router": "^3.3.0", + "@dvelop-sdk/core": "^3.0.0", + "@dvelop-sdk/identityprovider": "^5.0.0", "@types/express": "^4.17.13" } }, "packages/identityprovider": { "name": "@dvelop-sdk/identityprovider", - "version": "4.0.11", + "version": "5.0.0", "license": "Apache-2.0", "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } }, "packages/logging": { "name": "@dvelop-sdk/logging", - "version": "1.0.9", + "version": "1.1.0", "license": "Apache-2.0", "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } }, "packages/task": { "name": "@dvelop-sdk/task", - "version": "3.1.5", + "version": "4.0.0", "license": "Apache-2.0", "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } } } diff --git a/package.json b/package.json index 657fe92c..63e63080 100644 --- a/package.json +++ b/package.json @@ -26,17 +26,19 @@ "docs": "typedoc" }, "devDependencies": { - "@types/jest": "^27.0.2", - "@types/node": "^14.14.41", - "@typescript-eslint/eslint-plugin": "^4.16.1", - "@typescript-eslint/parser": "^4.16.1", - "eslint": "^7.21.0", - "eslint-plugin-jest": "^24.1.9", - "jest": "^27.5.1", + "@types/jest": "^29.0.0", + "@types/node": "^18.0.0", + "@eslint/js": "^9.0.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "eslint": "^9.0.0", + "eslint-plugin-jest": "^29.0.0", + "globals": "^16.0.0", + "jest": "^29.0.0", "license-checker": "^25.0.1", - "ts-jest": "^27.0.5", + "ts-jest": "^29.0.0", "ts-node": "^10.9.2", - "typedoc": "^0.22.13", - "typescript": "^4.1.2" + "typedoc": "^0.28.0", + "typescript": "^5.0.0" } } diff --git a/packages/app-router/package.json b/packages/app-router/package.json index f013c052..8acfed9d 100644 --- a/packages/app-router/package.json +++ b/packages/app-router/package.json @@ -1,7 +1,7 @@ { "name": "@dvelop-sdk/app-router", "description": "This package contains functionality for the App-Router in the d.velop cloud.", - "version": "3.2.9", + "version": "3.3.0", "license": "Apache-2.0", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -23,6 +23,6 @@ "license": "license-checker --production --onlyAllow Apache-2.0;MIT;ISC;BSD-2-Clause;BSD-3-Clause" }, "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } } diff --git a/packages/app-router/src/validate-cloud-center-event-signature/validate-cloud-center-event-signature.spec.ts b/packages/app-router/src/validate-cloud-center-event-signature/validate-cloud-center-event-signature.spec.ts index 5f99879c..3705af9c 100644 --- a/packages/app-router/src/validate-cloud-center-event-signature/validate-cloud-center-event-signature.spec.ts +++ b/packages/app-router/src/validate-cloud-center-event-signature/validate-cloud-center-event-signature.spec.ts @@ -44,7 +44,7 @@ describe("validateCloudCenterEventSignature", () => { ].forEach(testCase => { it("should calculate validate signature", () => { - expect(() => validateCloudCenterEventSignature(testCase.appSecret, testCase.params)).not.toThrowError(); + expect(() => validateCloudCenterEventSignature(testCase.appSecret, testCase.params)).not.toThrow(); }); }); diff --git a/packages/app-router/src/validate-cloud-center-event-signature/validate-cloud-center-event-signature.ts b/packages/app-router/src/validate-cloud-center-event-signature/validate-cloud-center-event-signature.ts index 75825557..9e98c630 100644 --- a/packages/app-router/src/validate-cloud-center-event-signature/validate-cloud-center-event-signature.ts +++ b/packages/app-router/src/validate-cloud-center-event-signature/validate-cloud-center-event-signature.ts @@ -6,7 +6,7 @@ import { DvelopSdkError } from "@dvelop-sdk/core"; * @category Error */ export class InvalidCloudCenterEventSignatureError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars + constructor() { super("Invalid CloudCenterEvent-signature: A cloud center event was recieved but signature was invalid."); Object.setPrototypeOf(this, InvalidCloudCenterEventSignatureError.prototype); @@ -66,7 +66,7 @@ export function validateCloudCenterEventSignature(appSecret: string, params: Val const sha256RequestString: string = createHash("sha256").update(normalizedRequestString).digest("hex"); const calculatedSignature: string = createHmac("sha256", Buffer.from(appSecret, "base64")).update(sha256RequestString).digest("hex"); validSignature = timingSafeEqual(Buffer.from(params.cloudCenterEventSignature), Buffer.from(calculatedSignature)); - } catch (e) { + } catch { throw new InvalidCloudCenterEventSignatureError(); } if (!validSignature) { diff --git a/packages/app-router/src/validate-request-signature/validate-request-signature.spec.ts b/packages/app-router/src/validate-request-signature/validate-request-signature.spec.ts index 41e09493..c74a5eed 100644 --- a/packages/app-router/src/validate-request-signature/validate-request-signature.spec.ts +++ b/packages/app-router/src/validate-request-signature/validate-request-signature.spec.ts @@ -17,11 +17,11 @@ describe("validateRequestSignature", () => { } ].forEach(testCase => { it(`should pass on: ${JSON.stringify(testCase)})`, () => { - expect(() => validateRequestSignature(testCase.appSecret, testCase.systemBaseUri, testCase.tenantId, testCase.signature)).not.toThrowError(); + expect(() => validateRequestSignature(testCase.appSecret, testCase.systemBaseUri, testCase.tenantId, testCase.signature)).not.toThrow(); }); it(`should pass on: ${JSON.stringify(testCase)})`, () => { - expect(() => validateDvelopContext(testCase.appSecret, { systemBaseUri: testCase.systemBaseUri, tenantId: testCase.tenantId, requestSignature: testCase.signature })).not.toThrowError(); + expect(() => validateDvelopContext(testCase.appSecret, { systemBaseUri: testCase.systemBaseUri, tenantId: testCase.tenantId, requestSignature: testCase.signature })).not.toThrow(); }); }); diff --git a/packages/app-router/src/validate-request-signature/validate-request-signature.ts b/packages/app-router/src/validate-request-signature/validate-request-signature.ts index 930c8a60..37db6055 100644 --- a/packages/app-router/src/validate-request-signature/validate-request-signature.ts +++ b/packages/app-router/src/validate-request-signature/validate-request-signature.ts @@ -6,7 +6,7 @@ import { DvelopContext, DvelopSdkError } from "@dvelop-sdk/core"; * @category Error */ export class InvalidRequestSignatureError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars + constructor(message: string) { super(message); Object.setPrototypeOf(this, InvalidRequestSignatureError.prototype); @@ -33,7 +33,7 @@ export function validateRequestSignature(appSecret: string, systemBaseUri: strin const binarySignatureSecret = Buffer.from(appSecret, "base64"); const computedHmac = crypto.createHmac("sha256", binarySignatureSecret).update(systemBaseUri + tenantId); validSignature = crypto.timingSafeEqual(Buffer.from(requestSignature), Buffer.from(computedHmac.digest("base64"))); - } catch (e) { + } catch { throw new InvalidRequestSignatureError("Invalid request-signature."); } if (!validSignature) { diff --git a/packages/app-router/tsconfig.build.json b/packages/app-router/tsconfig.build.json new file mode 100644 index 00000000..0c9b67d5 --- /dev/null +++ b/packages/app-router/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig-base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.spec.ts", "src/**/tmp-*.ts"] +} diff --git a/packages/app-router/tsconfig.json b/packages/app-router/tsconfig.json index 4cce3b68..3912f472 100644 --- a/packages/app-router/tsconfig.json +++ b/packages/app-router/tsconfig.json @@ -1,13 +1,10 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "outDir": "lib", /* Redirect output structure to the directory. */ - "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "noEmit": true, + "composite": false, + "declaration": false, + "declarationMap": false }, - "include": [ - "src/**/*" - ], - "exclude": [ - "src/**/*.spec.ts" - ] -} \ No newline at end of file + "include": ["src/**/*"] +} diff --git a/packages/business-objects/package.json b/packages/business-objects/package.json index 481becc1..799e5602 100644 --- a/packages/business-objects/package.json +++ b/packages/business-objects/package.json @@ -1,7 +1,7 @@ { "name": "@dvelop-sdk/business-objects", "description": "This package contains functionality for the BusinessObjects-App in the d.velop cloud.", - "version": "2.0.5", + "version": "3.0.0", "license": "Apache-2.0", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -23,6 +23,6 @@ "license": "license-checker --production --onlyAllow Apache-2.0;MIT;ISC;BSD-2-Clause;BSD-3-Clause" }, "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } } diff --git a/packages/business-objects/src/entities/create-entity/create-entity.spec.ts b/packages/business-objects/src/entities/create-entity/create-entity.spec.ts index 6094b14a..e6f37c90 100644 --- a/packages/business-objects/src/entities/create-entity/create-entity.spec.ts +++ b/packages/business-objects/src/entities/create-entity/create-entity.spec.ts @@ -1,58 +1,59 @@ -import { DvelopContext, DvelopHttpResponse as HttpResponse } from "@dvelop-sdk/core"; -import { CreateBoEntityParams, _createBoEntityFactory } from "./create-entity"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { CreateBoEntityParams, createBoEntity, onResponse } from "./create-entity"; +import { BusinessObjectsError } from "../../utils/business-objects-error"; -describe("createBoEntityFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("createBoEntity", () => { let context: DvelopContext; let params: CreateBoEntityParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - - params = { - modelName: "HOSPITALBASEDATA", - pluralEntityName: "employees", + context = { systemBaseUri: "someBaseUri" }; + params = { + modelName: "HOSPITALBASEDATA", + pluralEntityName: "employees", entity: { - "employeeid": "1", - "firstName": "John", - "lastName": "Dorian", - "jobTitel": "senior physician" + employeeId: "1", + firstName: "John Micheal", + lastName: "Dorian" } }; }); - it("should make correct request", async () => { - - const createBoEntity = _createBoEntityFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method POST and serialized entity", async () => { await createBoEntity(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "POST", - url: `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}`, - data: params.entity - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toEqual("/businessobjects/custom/HOSPITALBASEDATA/employees"); + expect(calledInit).toMatchObject({ method: "POST" }); + expect(JSON.parse(calledInit!.body as string)).toEqual(params.entity); + expect(calledOptions).toMatchObject({ onResponse: onResponse }); }); - it("should pass response to transform and return transform-result", async () => { + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await createBoEntity(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); + describe("onResponse", () => { - const createBoEntity = _createBoEntityFactory(mockHttpRequestFunction, mockTransformFunction); - await createBoEntity(context, params); + it("should resolve on a successful response", async () => { + await expect(onResponse(new Response(null, { status: 201 }))).resolves.toBeUndefined(); + }); - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should throw on a failed response", async () => { + await expect(onResponse(new Response(null, { status: 500 }))).rejects.toBeInstanceOf(BusinessObjectsError); + }); }); }); diff --git a/packages/business-objects/src/entities/create-entity/create-entity.ts b/packages/business-objects/src/entities/create-entity/create-entity.ts index 60fd3421..26d00a0d 100644 --- a/packages/business-objects/src/entities/create-entity/create-entity.ts +++ b/packages/business-objects/src/entities/create-entity/create-entity.ts @@ -1,5 +1,5 @@ -import { DvelopContext, DvelopHttpResponse as HttpResponse } from "@dvelop-sdk/core"; -import { HttpConfig, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/business-objects-error"; /** * Parameters for the {@link createBoEntity}-function. @@ -16,26 +16,12 @@ export interface CreateBoEntityParams { } /** - * Factory for {@link createBoEntity}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @template E Type for Entity to be created. - * @template R Return type of the {@link createBoEntity}-function. A corresponding transformFunction has to be supplied. + * Default `onResponse` provided to the {@link createBoEntity}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Entity */ -export function _createBoEntityFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: CreateBoEntityParams) => R -): (context: DvelopContext, params: CreateBoEntityParams) => Promise { - return async (context: DvelopContext, params: CreateBoEntityParams) => { - - const response = await httpRequestFunction(context, { - method: "POST", - url: `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}`, - data: params.entity - }); - - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } /** @@ -73,7 +59,7 @@ export function _createBoEntityFactory( * jobTitel: string; * } * - * await create({ + * await createBoEntity({ * systemBaseUri: "https://sacred-heart-hospital.d-velop.cloud", * authSessionId: "3f3c428d452" * },{ @@ -87,8 +73,20 @@ export function _createBoEntityFactory( * } * }); * ``` + * + * @category Entity */ -/* istanbul ignore next */ -export async function createBoEntity(context: DvelopContext, params: CreateBoEntityParams): Promise { - return await _createBoEntityFactory(_defaultHttpRequestFunction, () => { })(context, params); -} \ No newline at end of file +export async function createBoEntity(context: DvelopContext, params: CreateBoEntityParams): Promise; +export async function createBoEntity(context: DvelopContext, params: CreateBoEntityParams, options: DvelopOptions): Promise; +export async function createBoEntity( + context: DvelopContext, + params: CreateBoEntityParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}`, { + method: "POST", + body: JSON.stringify(params.entity) + }, options); +} diff --git a/packages/business-objects/src/entities/delete-entity/delete-entity.spec.ts b/packages/business-objects/src/entities/delete-entity/delete-entity.spec.ts index 8b0c941d..aae173d7 100644 --- a/packages/business-objects/src/entities/delete-entity/delete-entity.spec.ts +++ b/packages/business-objects/src/entities/delete-entity/delete-entity.spec.ts @@ -1,62 +1,71 @@ -import { DvelopContext, DvelopHttpResponse as HttpResponse } from "@dvelop-sdk/core"; -import { DeleteBoEntityParams, _deleteBoEntityFactory } from "./delete-entity"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { DeleteBoEntityParams, deleteBoEntity, onResponse } from "./delete-entity"; +import { BusinessObjectsError } from "../../utils/business-objects-error"; -describe("deleteBoEntityFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("deleteBoEntity", () => { let context: DvelopContext; let params: DeleteBoEntityParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "someBaseUri" }; params = { modelName: "HOSPITALBASEDATA", pluralEntityName: "employees", keyPropertyType: "string", - keyPropertyValue: "`1" + keyPropertyValue: "1" }; }); + it("should call dvelopFetch with method DELETE", async () => { + await deleteBoEntity(context, params); + + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + "/businessobjects/custom/HOSPITALBASEDATA/employees('1')", + { method: "DELETE" }, + expect.objectContaining({ onResponse: onResponse }) + ); + }); + [ { keyPropertyValue: "1", keyPropertyType: "string", expectedUrl: "/businessobjects/custom/HOSPITALBASEDATA/employees('1')" }, { keyPropertyValue: 2, keyPropertyType: "number", expectedUrl: "/businessobjects/custom/HOSPITALBASEDATA/employees(2)" }, { keyPropertyValue: "HiItsMeGuid", keyPropertyType: "guid", expectedUrl: "/businessobjects/custom/HOSPITALBASEDATA/employees(HiItsMeGuid)" } ].forEach(testCase => { - it("should make correct request", async () => { - - params.keyPropertyValue = testCase.keyPropertyValue; - params.keyPropertyType = testCase.keyPropertyType as "string" | "number" | "guid"; - - const deleteBoEntity = _deleteBoEntityFactory(mockHttpRequestFunction, mockTransformFunction); - await deleteBoEntity(context, params); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "DELETE", - url: testCase.expectedUrl + it(`should build url for keyPropertyType ${testCase.keyPropertyType}`, async () => { + await deleteBoEntity(context, { + ...params, + keyPropertyType: testCase.keyPropertyType as "string" | "number" | "guid", + keyPropertyValue: testCase.keyPropertyValue }); + expect(mockDvelopFetch.mock.calls[0][1]).toEqual(testCase.expectedUrl); }); }); - it("should pass response to transform and return transform-result", async () => { + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await deleteBoEntity(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); + describe("onResponse", () => { - const deleteBoEntity = _deleteBoEntityFactory(mockHttpRequestFunction, mockTransformFunction); - await deleteBoEntity(context, params); + it("should resolve on a successful response", async () => { + await expect(onResponse(new Response(null, { status: 204 }))).resolves.toBeUndefined(); + }); - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should throw on a failed response", async () => { + await expect(onResponse(new Response(null, { status: 500 }))).rejects.toBeInstanceOf(BusinessObjectsError); + }); }); }); diff --git a/packages/business-objects/src/entities/delete-entity/delete-entity.ts b/packages/business-objects/src/entities/delete-entity/delete-entity.ts index 3ffd42a7..27f4015d 100644 --- a/packages/business-objects/src/entities/delete-entity/delete-entity.ts +++ b/packages/business-objects/src/entities/delete-entity/delete-entity.ts @@ -1,5 +1,5 @@ -import { DvelopContext, DvelopHttpResponse as HttpResponse } from "@dvelop-sdk/core"; -import { HttpConfig, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/business-objects-error"; /** * Parameters for the {@link deleteBoEntity}-function. @@ -17,32 +17,12 @@ export interface DeleteBoEntityParams { } /** - * Factory for {@link deleteBoEntity}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @template E Return type of the {@link deleteBoEntity}-function. A corresponding transformFunction has to be supplied. + * Default `onResponse` provided to the {@link deleteBoEntity}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Entity */ -export function _deleteBoEntityFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: DeleteBoEntityParams) => T -): (context: DvelopContext, params: DeleteBoEntityParams) => Promise { - return async (context: DvelopContext, params: DeleteBoEntityParams) => { - - let urlEntityKeyValue; - if (params.keyPropertyType === "number" || params.keyPropertyType === "guid") { - urlEntityKeyValue = params.keyPropertyValue; - } else { - urlEntityKeyValue = `'${params.keyPropertyValue}'`; - } - - - const response = await httpRequestFunction(context, { - method: "DELETE", - url: `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}(${urlEntityKeyValue})` - }); - - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } /** @@ -62,21 +42,14 @@ export function _deleteBoEntityFactory( * keyPropertyValue: 1 * }); * ``` - * --- - * You can also write your own function, for example to get a notification, if the entity requested for deletion doesn't exist. + * --- + * You can supply your own `onResponse` to react to the raw response, for example to get a + * notification if the entity requested for deletion didn't exist. * @example * ```typescript * import { deleteBoEntity } from "@dvelop-sdk/business-objects"; * - * const myDeleteFunction = _deleteBoEntityFactory(_defaultHttpRequestFunction, (response: HttpResponse) => { - * if(response.status === 204) { - * return "Entity does not exist."; - * } else { - * return "Entity was deleted."; - * } - * }); - * - * const responseMessage: string = await myDeleteFunction({ + * const responseMessage: string = await deleteBoEntity({ * systemBaseUri: "https://sacred-heart-hospital.d-velop.cloud", * authSessionId: "3f3c428d452" * },{ @@ -84,12 +57,31 @@ export function _deleteBoEntityFactory( * pluralEntityName: "employees", * keyPropertyType: "number", //"string", "number" or "guid" * keyPropertyValue: 3 + * }, { + * onResponse: (response) => response.status === 204 ? "Entity does not exist." : "Entity was deleted." * }); * * console.log(responseMessage); // Entity does not exist. * ``` + * + * @category Entity */ -/* istanbul ignore next */ -export async function deleteBoEntity(context: DvelopContext, params: DeleteBoEntityParams): Promise { - return await _deleteBoEntityFactory(_defaultHttpRequestFunction, () => { })(context, params); -} \ No newline at end of file +export async function deleteBoEntity(context: DvelopContext, params: DeleteBoEntityParams): Promise; +export async function deleteBoEntity(context: DvelopContext, params: DeleteBoEntityParams, options: DvelopOptions): Promise; +export async function deleteBoEntity( + context: DvelopContext, + params: DeleteBoEntityParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + + let urlEntityKeyValue: string | number; + if (params.keyPropertyType === "number" || params.keyPropertyType === "guid") { + urlEntityKeyValue = params.keyPropertyValue; + } else { + urlEntityKeyValue = `'${params.keyPropertyValue}'`; + } + + return dvelopFetch(context, `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}(${urlEntityKeyValue})`, { method: "DELETE" }, options); +} diff --git a/packages/business-objects/src/entities/get-entities/get-entities.spec.ts b/packages/business-objects/src/entities/get-entities/get-entities.spec.ts index d526242c..070955bb 100644 --- a/packages/business-objects/src/entities/get-entities/get-entities.spec.ts +++ b/packages/business-objects/src/entities/get-entities/get-entities.spec.ts @@ -1,143 +1,93 @@ -import { DvelopContext, DvelopHttpResponse as HttpResponse } from "@dvelop-sdk/core"; -import { GetBoEntitiesParams, _getBoEntitiesDefaultTransformFunctionFactory, _getBoEntitiesFactory, GetBoEntitiesResultPage } from "./get-entities"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { GetBoEntitiesParams, GetBoEntitiesResultPage, buildResultPage, defaultOnResponseFactory, getBoEntities } from "./get-entities"; -describe("getBoEntitiesFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("getBoEntities", () => { let context: DvelopContext; let params: GetBoEntitiesParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "https://someBaseUri" }; params = { modelName: "HOSPITALBASEDATA", pluralEntityName: "employees" }; }); - it("should make correct request", async () => { + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } - const getBoEntities = _getBoEntitiesFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method GET", async () => { await getBoEntities(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}`, - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + "/businessobjects/custom/HOSPITALBASEDATA/employees", + { method: "GET" }, + expect.objectContaining({ onResponse: expect.any(Function) }) + ); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await getBoEntities(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - const getBoEntities = _getBoEntitiesFactory(mockHttpRequestFunction, mockTransformFunction); - const result = await getBoEntities(context, params); + describe("onResponse / buildResultPage", () => { - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); - expect(result).toEqual(transformResult); - }); + it("should set value and no getNextPage when there is no nextLink", async () => { + const data = { value: [{ employeeId: "1" }, { employeeId: "2" }] }; + const result: GetBoEntitiesResultPage = await defaultOnResponseFactory(context)(jsonResponse(data)); - describe("getBoEntitiesDefaultTransformFunction", () => { - - it("should set entities", async () => { - - const response: any = { - value: [ - { - "employeeid": "1", - "firstName": "John", - "lastName": "Dorian", - "jobTitel": "senior physician" - }, - { - "employeeid": "2", - "firstName": "Christopher", - "lastName": "Turk", - "jobTitel": "chief surgeon" - } - ] - }; - - mockHttpRequestFunction.mockResolvedValue({ data: response } as HttpResponse); - - const getBoEntities = _getBoEntitiesFactory(mockHttpRequestFunction, _getBoEntitiesDefaultTransformFunctionFactory(mockHttpRequestFunction)); - const result: GetBoEntitiesResultPage = await getBoEntities(context, params); - - response.value.forEach((entity: any, i: number) => { - expect(result.value[i]).toHaveProperty("employeeid", entity.employeeid); - }); - expect(result).not.toHaveProperty("getNextPage"); + expect(result.value).toEqual(data.value); + expect(result.getNextPage).toBeUndefined(); }); - it("should set getNextPage function on @odata.nextLink-property", async () => { - - const response: any = { - value: [ - { - "employeeid": "1", - "firstName": "John", - "lastName": "Dorian", - "jobTitel": "senior physician" - } - ], - "@odata.nextLink": "HiItsMeNextLink" - }; - - mockHttpRequestFunction.mockResolvedValue({ data: response } as HttpResponse); - - const getBoEntities = _getBoEntitiesFactory(mockHttpRequestFunction, _getBoEntitiesDefaultTransformFunctionFactory(mockHttpRequestFunction)); - const result: GetBoEntitiesResultPage = await getBoEntities(context, params); - - expect(result).toHaveProperty("getNextPage"); - - const response2: any = { - value: [ - { - "employeeid": "2", - "firstName": "Christopher", - "lastName": "Turk", - "jobTitel": "chief surgeon" - } - ] - }; - mockHttpRequestFunction.mockResolvedValue({ data: response2 } as HttpResponse); - - let page2 = await result.getNextPage(); - - expect(page2.value).toContain(response2.value[0]); - expect(mockHttpRequestFunction).toBeCalledTimes(2); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "HiItsMeNextLink" - }); + it("should return an empty value array", async () => { + const result = await buildResultPage(jsonResponse({ value: [] }), context); + expect(result.value).toHaveLength(0); }); - it("should return empty array on no value", async () => { + it("should set getNextPage when a nextLink is present and follow it", async () => { + const page1 = { value: [{ employeeId: "1" }], "@odata.nextLink": "/businessobjects/custom/HOSPITALBASEDATA/employees?skip=1" }; + const result = await buildResultPage(jsonResponse(page1), context); - const response: any = { - value: [] - }; + expect(result.getNextPage).toBeDefined(); - mockHttpRequestFunction.mockResolvedValue({ data: response } as HttpResponse); + const page2 = { value: [{ employeeId: "2" }] }; + mockDvelopFetch.mockResolvedValue({ value: page2.value } as any); - const getBoEntities = _getBoEntitiesFactory(mockHttpRequestFunction, _getBoEntitiesDefaultTransformFunctionFactory(mockHttpRequestFunction)); - const result: GetBoEntitiesResultPage = await getBoEntities(context, params); + const next = await result.getNextPage!(); - expect(result).toHaveProperty("value"); - expect(result.value).toHaveLength(0); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + "/businessobjects/custom/HOSPITALBASEDATA/employees?skip=1", + { method: "GET" }, + expect.objectContaining({ onResponse: expect.any(Function) }) + ); + expect(next.value).toEqual(page2.value); + }); + + it("should strip the systemBaseUri from an absolute nextLink", async () => { + const page1 = { value: [{ employeeId: "1" }], "@odata.nextLink": `${context.systemBaseUri}/businessobjects/custom/HOSPITALBASEDATA/employees?skip=1` }; + const result = await buildResultPage(jsonResponse(page1), context); + + mockDvelopFetch.mockResolvedValue({ value: [] } as any); + await result.getNextPage!(); + + expect(mockDvelopFetch.mock.calls[0][1]).toEqual("/businessobjects/custom/HOSPITALBASEDATA/employees?skip=1"); }); }); }); diff --git a/packages/business-objects/src/entities/get-entities/get-entities.ts b/packages/business-objects/src/entities/get-entities/get-entities.ts index 14fb6ebe..1b0a181f 100644 --- a/packages/business-objects/src/entities/get-entities/get-entities.ts +++ b/packages/business-objects/src/entities/get-entities/get-entities.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/business-objects-error"; /** * Parameters for the {@link getBoEntities}-function. @@ -17,58 +17,48 @@ export interface GetBoEntitiesParams { * @category Entity */ export interface GetBoEntitiesResultPage { - /** Array of entitiess found */ - value: E[] + /** Array of entities found */ + value: E[]; /** Function that returns the next page. Undefined if there is none. */ getNextPage?: () => Promise>; } /** - * Default transform-function provided to the {@link getBoEntities}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @template E Return type + * Builds a {@link GetBoEntitiesResultPage} from a response and wires up paging via the + * OData `@odata.nextLink`. The `context` is captured so that `getNextPage` can issue a + * follow-up request. * @internal * @category Entity */ -export function _getBoEntitiesDefaultTransformFunctionFactory(httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise): (response: HttpResponse, context: DvelopContext, params: GetBoEntitiesParams) => GetBoEntitiesResultPage { - return (response: HttpResponse, context: DvelopContext, params: GetBoEntitiesParams) => { +export async function buildResultPage(response: Response, context: DvelopContext): Promise> { - let result: GetBoEntitiesResultPage = { - value: response.data.value - }; + await ensureSuccessResponse(response); + const data: any = await response.json(); - if (response.data["@odata.nextLink"]) { - result.getNextPage = async () => { - const nextResponse: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: response.data["@odata.nextLink"] - }); - return _getBoEntitiesDefaultTransformFunctionFactory(httpRequestFunction)(nextResponse, context, params); - }; - } - - return result; + const result: GetBoEntitiesResultPage = { + value: data.value }; + + const nextLink: string | undefined = data["@odata.nextLink"]; + if (nextLink) { + const systemBaseUri: string = context.systemBaseUri ?? ""; + const nextPath: string = systemBaseUri && nextLink.startsWith(systemBaseUri) ? nextLink.slice(systemBaseUri.length) : nextLink; + result.getNextPage = () => dvelopFetch(context, nextPath, { method: "GET" }, { + onResponse: (nextResponse: Response) => buildResultPage(nextResponse, context) + }); + } + + return result; } /** - * Factory for {@link getBoEntities}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @template E Return type of the {@link getBoEntities}-function. A corresponding transformFunction has to be supplied. + * Default `onResponse` provided to the {@link getBoEntities}-function. Captures the `context` + * so that paging is possible. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Entity */ -export function _getBoEntitiesFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: GetBoEntitiesParams) => GetBoEntitiesResultPage -): (context: DvelopContext, params: GetBoEntitiesParams) => Promise> { - return async (context: DvelopContext, params: GetBoEntitiesParams) => { - - const response = await httpRequestFunction(context, { - method: "GET", - url: `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}` - }); - - return transformFunction(response, context, params); - }; +export function defaultOnResponseFactory(context: DvelopContext): (response: Response) => Promise> { + return (response: Response) => buildResultPage(response, context); } /** @@ -79,57 +69,59 @@ export function _getBoEntitiesFactory( * ```typescript * import { getBoEntities } from "@dvelop-sdk/business-objects"; * - * const resultPage: GetEntitiesResultPage = await getBoEntities({ + * const resultPage: GetBoEntitiesResultPage = await getBoEntities({ * systemBaseUri: "https://sacred-heart-hospital.d-velop.cloud", * authSessionId: "3f3c428d452" * },{ * modelName: "HOSPITALBASEDATA", * pluralEntityName: "employees" * }); - * - * let employees = await resultPage.value; + * + * let employees = resultPage.value; * * // Use this for paging - * while (resultPage.getNextPage) { - * const nextPage: GetBoEntitiesResultPage = await resultPage.getNextPage(); - * employees = employees.concat(nextPage.value); + * let page = resultPage; + * while (page.getNextPage) { + * page = await page.getNextPage(); + * employees = employees.concat(page.value); * } * ``` * --- * You can also use generics: - * * @example + * @example * ```typescript * import { getBoEntities } from "@dvelop-sdk/business-objects"; * - * interface Employee { + * interface Employee { * employeeId: string; * firstName: string; * lastName: string; * jobTitel: string; * } - * - * const resultPage: GetBoEntitiesResultPage = await getBoEntities({ + * + * const resultPage: GetBoEntitiesResultPage = await getBoEntities({ * systemBaseUri: "https://sacred-heart-hospital.d-velop.cloud", * authSessionId: "3f3c428d452" * }, { * modelName: "HOSPITALBASEDATA", * pluralEntityName: "employees" * }); - * - * let employees: Employee[] = await resultPage.value; - * - * // Use this for paging - * while (resultPage.getNextPage) { - * const nextPage: GetBoEntitiesResultPage = await resultPage.getNextPage(); - * employees = employees.concat(nextPage.value); - * } - * - * employees.forEach(e => console.log(e.lastName)); + * + * resultPage.value.forEach(e => console.log(e.lastName)); * // Dorian * // Turk * ``` + * + * @category Entity */ -/* istanbul ignore next */ -export async function getBoEntities(context: DvelopContext, params: GetBoEntitiesParams): Promise> { - return await _getBoEntitiesFactory(_defaultHttpRequestFunction, _getBoEntitiesDefaultTransformFunctionFactory(_defaultHttpRequestFunction))(context, params); -} \ No newline at end of file +export async function getBoEntities(context: DvelopContext, params: GetBoEntitiesParams): Promise>; +export async function getBoEntities(context: DvelopContext, params: GetBoEntitiesParams, options: DvelopOptions): Promise; +export async function getBoEntities( + context: DvelopContext, + params: GetBoEntitiesParams, + options: DvelopOptions> = { + onResponse: defaultOnResponseFactory(context) + } +): Promise> { + return dvelopFetch(context, `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}`, { method: "GET" }, options); +} diff --git a/packages/business-objects/src/entities/get-entity/get-entity.spec.ts b/packages/business-objects/src/entities/get-entity/get-entity.spec.ts index a9679572..f361c1ed 100644 --- a/packages/business-objects/src/entities/get-entity/get-entity.spec.ts +++ b/packages/business-objects/src/entities/get-entity/get-entity.spec.ts @@ -1,101 +1,78 @@ -import { DvelopContext, DvelopHttpResponse as HttpResponse } from "@dvelop-sdk/core"; -import { GetBoEntityParams, _getBoEntityDefaultTransformFunction, _getBoEntityFactory } from "./get-entity"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { GetBoEntityParams, getBoEntity, onResponse } from "./get-entity"; -describe("getBoEntityFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("getBoEntity", () => { let context: DvelopContext; let params: GetBoEntityParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "someBaseUri" }; params = { modelName: "HOSPITALBASEDATA", pluralEntityName: "employees", keyPropertyType: "string", - keyPropertyValue: "1", + keyPropertyValue: "1" }; }); + it("should call dvelopFetch with method GET", async () => { + await getBoEntity(context, params); + + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + "/businessobjects/custom/HOSPITALBASEDATA/employees('1')", + { method: "GET" }, + expect.objectContaining({ onResponse: onResponse }) + ); + }); + [ { keyPropertyValue: "1", keyPropertyType: "string", expectedUrl: "/businessobjects/custom/HOSPITALBASEDATA/employees('1')" }, { keyPropertyValue: 2, keyPropertyType: "number", expectedUrl: "/businessobjects/custom/HOSPITALBASEDATA/employees(2)" }, { keyPropertyValue: "HiItsMeGuid", keyPropertyType: "guid", expectedUrl: "/businessobjects/custom/HOSPITALBASEDATA/employees(HiItsMeGuid)" } ].forEach(testCase => { - it("should make correct request", async () => { - - params.keyPropertyValue = testCase.keyPropertyValue; - params.keyPropertyType = testCase.keyPropertyType as "string" | "number" | "guid"; - - const getBoEntity = _getBoEntityFactory(mockHttpRequestFunction, mockTransformFunction); - await getBoEntity(context, params); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: testCase.expectedUrl + it(`should build url for keyPropertyType ${testCase.keyPropertyType}`, async () => { + await getBoEntity(context, { + ...params, + keyPropertyType: testCase.keyPropertyType as "string" | "number" | "guid", + keyPropertyValue: testCase.keyPropertyValue }); + expect(mockDvelopFetch.mock.calls[0][1]).toEqual(testCase.expectedUrl); }); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const getBoEntities = _getBoEntityFactory(mockHttpRequestFunction, mockTransformFunction); - const result = await getBoEntities(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); - expect(result).toEqual(transformResult); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await getBoEntity(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - describe("getBoEntityDefaultTransformFunction", () => { - - it("should transform correctly", async () => { - - const data: any = { - "employeeid": "1", - "firstName": "John", - "lastName": "Dorian", - "jobTitel": "senior physician" - }; + describe("onResponse", () => { - mockHttpRequestFunction.mockResolvedValue({ data: data } as HttpResponse); + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } - const getBoEntity = _getBoEntityFactory(mockHttpRequestFunction, _getBoEntityDefaultTransformFunction); - const result = await getBoEntity(context, params); - - expect(result).toHaveProperty("employeeid", data.employeeid); + it("should return the entity", async () => { + const entity = { employeeId: "1", firstName: "John Micheal", lastName: "Dorian" }; + const result = await onResponse(jsonResponse(entity)); + expect(result).toEqual(entity); }); - it("should remove @odata.context", async () => { - - const data: any = { - "@odata.context": "HiItsMeOdataContext", - "employeeid": "1", - "firstName": "John", - "lastName": "Dorian", - "jobTitel": "senior physician" - }; - - mockHttpRequestFunction.mockResolvedValue({ data: data } as HttpResponse); - - const getBoEntity = _getBoEntityFactory(mockHttpRequestFunction, _getBoEntityDefaultTransformFunction); - const result = await getBoEntity(context, params); - - expect(result).not.toHaveProperty("@odata.context"); + it("should strip @odata.context", async () => { + const result = await onResponse(jsonResponse({ "@odata.context": "someContext", employeeId: "1" })); + expect(result["@odata.context"]).toBeUndefined(); + expect(result.employeeId).toEqual("1"); }); }); }); diff --git a/packages/business-objects/src/entities/get-entity/get-entity.ts b/packages/business-objects/src/entities/get-entity/get-entity.ts index a0c60320..be6c4717 100644 --- a/packages/business-objects/src/entities/get-entity/get-entity.ts +++ b/packages/business-objects/src/entities/get-entity/get-entity.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/business-objects-error"; /** * Parameters for the {@link getBoEntity}-function. @@ -17,45 +17,17 @@ export interface GetBoEntityParams { } /** - * Default transform-function provided to the {@link getBoEntity}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @template E Return type + * Default `onResponse` provided to the {@link getBoEntity}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Entity */ -export function _getBoEntityDefaultTransformFunction(response: HttpResponse, _: DvelopContext, __: GetBoEntityParams): E { - const entity: any = response.data; - if (typeof entity === "object" && entity["@odata.context"]) { +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + const entity: any = await response.json(); + if (entity && typeof entity === "object" && entity["@odata.context"]) { delete entity["@odata.context"]; } - return response.data as E; -} - -/** - * Factory for {@link getBoEntity}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @template E Return type of the {@link getBoEntity}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category Entity - */ -export function _getBoEntityFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: GetBoEntityParams) => E -): (context: DvelopContext, params: GetBoEntityParams) => Promise { - return async (context: DvelopContext, params: GetBoEntityParams) => { - - let urlEntityKeyValue; - if (params.keyPropertyType === "number" || params.keyPropertyType === "guid") { - urlEntityKeyValue = params.keyPropertyValue; - } else { - urlEntityKeyValue = `'${params.keyPropertyValue}'`; - } - - const response = await httpRequestFunction(context, { - method: "GET", - url: `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}(${urlEntityKeyValue})` - }); - - return transformFunction(response, context, params); - }; + return entity; } /** @@ -102,8 +74,25 @@ export function _getBoEntityFactory( * * console.log(jd.lastName); // Dorian * ``` + * + * @category Entity */ -/* istanbul ignore next */ -export async function getBoEntity(context: DvelopContext, params: GetBoEntityParams): Promise { - return await _getBoEntityFactory(_defaultHttpRequestFunction, _getBoEntityDefaultTransformFunction)(context, params); -} \ No newline at end of file +export async function getBoEntity(context: DvelopContext, params: GetBoEntityParams): Promise; +export async function getBoEntity(context: DvelopContext, params: GetBoEntityParams, options: DvelopOptions): Promise; +export async function getBoEntity( + context: DvelopContext, + params: GetBoEntityParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + + let urlEntityKeyValue: string | number; + if (params.keyPropertyType === "number" || params.keyPropertyType === "guid") { + urlEntityKeyValue = params.keyPropertyValue; + } else { + urlEntityKeyValue = `'${params.keyPropertyValue}'`; + } + + return dvelopFetch(context, `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}(${urlEntityKeyValue})`, { method: "GET" }, options); +} diff --git a/packages/business-objects/src/entities/update-entity/update-entity.spec.ts b/packages/business-objects/src/entities/update-entity/update-entity.spec.ts index a656cbc0..03ae7a28 100644 --- a/packages/business-objects/src/entities/update-entity/update-entity.spec.ts +++ b/packages/business-objects/src/entities/update-entity/update-entity.spec.ts @@ -1,66 +1,72 @@ -import { DvelopContext, DvelopHttpResponse as HttpResponse } from "@dvelop-sdk/core"; -import { UpdateBoEntityParams, _updateBoEntityFactory } from "./update-entity"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { UpdateBoEntityParams, updateBoEntity, onResponse } from "./update-entity"; +import { BusinessObjectsError } from "../../utils/business-objects-error"; -describe("updateBoEntityFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("updateBoEntity", () => { let context: DvelopContext; let params: UpdateBoEntityParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "someBaseUri" }; params = { modelName: "HOSPITALBASEDATA", pluralEntityName: "employees", keyPropertyType: "string", keyPropertyValue: "1", - entityChange: { - "firstName": "J.D." - } + entityChange: { firstName: "J.D." } }; }); + it("should call dvelopFetch with method PATCH and serialized entityChange", async () => { + await updateBoEntity(context, params); + + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toEqual("/businessobjects/custom/HOSPITALBASEDATA/employees('1')"); + expect(calledInit).toMatchObject({ method: "PATCH" }); + expect(JSON.parse(calledInit!.body as string)).toEqual(params.entityChange); + expect(calledOptions).toMatchObject({ onResponse: onResponse }); + }); + [ { keyPropertyValue: "1", keyPropertyType: "string", expectedUrl: "/businessobjects/custom/HOSPITALBASEDATA/employees('1')" }, { keyPropertyValue: 2, keyPropertyType: "number", expectedUrl: "/businessobjects/custom/HOSPITALBASEDATA/employees(2)" }, { keyPropertyValue: "HiItsMeGuid", keyPropertyType: "guid", expectedUrl: "/businessobjects/custom/HOSPITALBASEDATA/employees(HiItsMeGuid)" } ].forEach(testCase => { - it("should make correct request", async () => { - - params.keyPropertyValue = testCase.keyPropertyValue; - params.keyPropertyType = testCase.keyPropertyType as "string" | "number" | "guid"; - - const deleteBoEntity = _updateBoEntityFactory(mockHttpRequestFunction, mockTransformFunction); - await deleteBoEntity(context, params); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "PATCH", - url: testCase.expectedUrl, - data: params.entityChange + it(`should build url for keyPropertyType ${testCase.keyPropertyType}`, async () => { + await updateBoEntity(context, { + ...params, + keyPropertyType: testCase.keyPropertyType as "string" | "number" | "guid", + keyPropertyValue: testCase.keyPropertyValue }); + expect(mockDvelopFetch.mock.calls[0][1]).toEqual(testCase.expectedUrl); }); }); - it("should pass response to transform and return transform-result", async () => { + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await updateBoEntity(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); + describe("onResponse", () => { - const updateBoEntity = _updateBoEntityFactory(mockHttpRequestFunction, mockTransformFunction); - await updateBoEntity(context, params); + it("should resolve on a successful response", async () => { + await expect(onResponse(new Response(null, { status: 204 }))).resolves.toBeUndefined(); + }); - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should throw on a failed response", async () => { + await expect(onResponse(new Response(null, { status: 500 }))).rejects.toBeInstanceOf(BusinessObjectsError); + }); }); }); diff --git a/packages/business-objects/src/entities/update-entity/update-entity.ts b/packages/business-objects/src/entities/update-entity/update-entity.ts index 6113a9bf..1e9adac1 100644 --- a/packages/business-objects/src/entities/update-entity/update-entity.ts +++ b/packages/business-objects/src/entities/update-entity/update-entity.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/business-objects-error"; /** * Parameters for the {@link updateBoEntity}-function. @@ -20,32 +20,12 @@ export interface UpdateBoEntityParams { } /** - * Factory for {@link updateBoEntity}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @template E Return type of the {@link updateBoEntity}-function. A corresponding transformFunction has to be supplied. + * Default `onResponse` provided to the {@link updateBoEntity}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Entity */ -export function _updateBoEntityFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: UpdateBoEntityParams) => R -): (context: DvelopContext, params: UpdateBoEntityParams) => Promise { - return async (context: DvelopContext, params: UpdateBoEntityParams) => { - - let urlEntityKeyValue; - if (params.keyPropertyType === "number" || params.keyPropertyType === "guid") { - urlEntityKeyValue = params.keyPropertyValue; - } else { - urlEntityKeyValue = `'${params.keyPropertyValue}'`; - } - - const response = await httpRequestFunction(context, { - method: "PATCH", - url: `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}(${urlEntityKeyValue})`, - data: params.entityChange - }); - - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } /** @@ -95,8 +75,28 @@ export function _updateBoEntityFactory( * } * }); * ``` + * + * @category Entity */ -/* istanbul ignore next */ -export async function updateBoEntity(context: DvelopContext, params: UpdateBoEntityParams): Promise { - return await _updateBoEntityFactory(_defaultHttpRequestFunction, () => { })(context, params); +export async function updateBoEntity(context: DvelopContext, params: UpdateBoEntityParams): Promise; +export async function updateBoEntity(context: DvelopContext, params: UpdateBoEntityParams, options: DvelopOptions): Promise; +export async function updateBoEntity( + context: DvelopContext, + params: UpdateBoEntityParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + + let urlEntityKeyValue: string | number; + if (params.keyPropertyType === "number" || params.keyPropertyType === "guid") { + urlEntityKeyValue = params.keyPropertyValue; + } else { + urlEntityKeyValue = `'${params.keyPropertyValue}'`; + } + + return dvelopFetch(context, `/businessobjects/custom/${params.modelName}/${params.pluralEntityName}(${urlEntityKeyValue})`, { + method: "PATCH", + body: JSON.stringify(params.entityChange) + }, options); } diff --git a/packages/business-objects/src/index.ts b/packages/business-objects/src/index.ts index 12d23f2b..45ea69f4 100644 --- a/packages/business-objects/src/index.ts +++ b/packages/business-objects/src/index.ts @@ -24,11 +24,11 @@ * @module business-objects */ -//Utils -export { DvelopContext, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError } from "@dvelop-sdk/core"; -export { BusinessObjectsError } from "./utils/http"; -export * as internals from "./internal"; +// Utils +export { DvelopContext, DvelopOptions, dvelopFetch, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError } from "@dvelop-sdk/core"; +export { BusinessObjectsError, NotImplementedError, BusinessObjectsErrorDto } from "./utils/business-objects-error"; +// Entities export { GetBoEntitiesParams, getBoEntities, GetBoEntitiesResultPage } from "./entities/get-entities/get-entities"; export { GetBoEntityParams, getBoEntity } from "./entities/get-entity/get-entity"; export { CreateBoEntityParams, createBoEntity } from "./entities/create-entity/create-entity"; diff --git a/packages/business-objects/src/internal.ts b/packages/business-objects/src/internal.ts deleted file mode 100644 index 5ffe141c..00000000 --- a/packages/business-objects/src/internal.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { _defaultHttpRequestFunctionFactory, _defaultHttpRequestFunction, BusinessObjectsErrorDto } from "./utils/http"; - -export { _getBoEntitiesFactory, _getBoEntitiesDefaultTransformFunctionFactory } from "./entities/get-entities/get-entities"; -export { _getBoEntityFactory, _getBoEntityDefaultTransformFunction } from "./entities/get-entity/get-entity"; -export { _createBoEntityFactory } from "./entities/create-entity/create-entity"; -export { _updateBoEntityFactory } from "./entities/update-entity/update-entity"; -export { _deleteBoEntityFactory } from "./entities/delete-entity/delete-entity"; \ No newline at end of file diff --git a/packages/business-objects/src/utils/business-objects-error.spec.ts b/packages/business-objects/src/utils/business-objects-error.spec.ts new file mode 100644 index 00000000..0ed8ba41 --- /dev/null +++ b/packages/business-objects/src/utils/business-objects-error.spec.ts @@ -0,0 +1,146 @@ +import { BadInputError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; +import { BusinessObjectsError, BusinessObjectsErrorDto, NotImplementedError, ensureSuccessResponse, getErrorString } from "./business-objects-error"; + +function jsonResponse(dto: any, status: number): Response { + return new Response(JSON.stringify(dto), { status, headers: { "Content-Type": "application/json" } }); +} + +const errorDto: BusinessObjectsErrorDto = { + error: { + code: "HiItsMeErrorCode", + message: "HiItsMeErrorMessage", + details: [ + { code: "HiItsMeDetail1Code", message: "HiItsMeDetail1Message" }, + { code: "HiItsMeDetail2Code", message: "HiItsMeDetail2Message" } + ] + } +}; + +describe("getErrorString", () => { + + it("should build a message including code, message and details", () => { + const result = getErrorString(errorDto)!; + expect(result).toContain(errorDto.error.code); + expect(result).toContain(errorDto.error.message); + expect(result).toContain(errorDto.error.details![0].code); + expect(result).toContain(errorDto.error.details![0].message); + expect(result).toContain(errorDto.error.details![1].code); + expect(result).toContain(errorDto.error.details![1].message); + }); + + it("should return null when no error is present", () => { + expect(getErrorString({} as BusinessObjectsErrorDto)).toBeNull(); + }); +}); + +describe("ensureSuccessResponse", () => { + + it("should not throw on 200", async () => { + await expect(ensureSuccessResponse(new Response("anything", { status: 200 }))).resolves.toBeUndefined(); + }); + + it("should not throw on 204", async () => { + await expect(ensureSuccessResponse(new Response(null, { status: 204 }))).resolves.toBeUndefined(); + }); + + describe("on bad-input status codes", () => { + + [400, 409, 413, 414, 429, 431].forEach(status => { + + it(`should throw BadInputError with dto message on status ${status}`, async () => { + const err: any = await ensureSuccessResponse(jsonResponse(errorDto, status)).catch((e: any) => e); + expect(err).toBeInstanceOf(BadInputError); + expect(err.message).toContain(errorDto.error.code); + expect(err.message).toContain(errorDto.error.message); + }); + + it(`should throw generic BadInputError on missing dto on status ${status}`, async () => { + const err: any = await ensureSuccessResponse(new Response(null, { status })).catch((e: any) => e); + expect(err).toBeInstanceOf(BadInputError); + expect(err.message).toEqual("BusinessObjects-App responded with Status 400 indicating bad Request-Parameters."); + }); + }); + }); + + describe("on statusCode 401", () => { + + it("should throw UnauthorizedError with dto message", async () => { + const err: any = await ensureSuccessResponse(jsonResponse(errorDto, 401)).catch((e: any) => e); + expect(err).toBeInstanceOf(UnauthorizedError); + expect(err.message).toContain(errorDto.error.code); + expect(err.message).toContain(errorDto.error.message); + }); + + it("should throw UnauthorizedError with string body", async () => { + const err: any = await ensureSuccessResponse(new Response("HiItsMeErrorReason", { status: 401 })).catch((e: any) => e); + expect(err).toBeInstanceOf(UnauthorizedError); + expect(err.message).toEqual("HiItsMeErrorReason"); + }); + + it("should throw generic UnauthorizedError on missing body", async () => { + const err: any = await ensureSuccessResponse(new Response(null, { status: 401 })).catch((e: any) => e); + expect(err).toBeInstanceOf(UnauthorizedError); + expect(err.message).toEqual("BusinessObjects-App responded with Status 401 indicating bad authSessionId."); + }); + }); + + describe("on statusCode 403", () => { + + it("should throw ForbiddenError with dto message", async () => { + const err: any = await ensureSuccessResponse(jsonResponse(errorDto, 403)).catch((e: any) => e); + expect(err).toBeInstanceOf(ForbiddenError); + expect(err.message).toContain(errorDto.error.code); + }); + + it("should throw generic ForbiddenError on missing dto", async () => { + const err: any = await ensureSuccessResponse(new Response(null, { status: 403 })).catch((e: any) => e); + expect(err).toBeInstanceOf(ForbiddenError); + expect(err.message).toEqual("BusinessObjects-App responded with Status 403 indicating a forbidden action."); + }); + }); + + describe("on statusCode 404", () => { + + it("should throw NotFoundError with dto message", async () => { + const err: any = await ensureSuccessResponse(jsonResponse(errorDto, 404)).catch((e: any) => e); + expect(err).toBeInstanceOf(NotFoundError); + expect(err.message).toContain(errorDto.error.code); + }); + + it("should throw generic NotFoundError on missing dto", async () => { + const err: any = await ensureSuccessResponse(new Response(null, { status: 404 })).catch((e: any) => e); + expect(err).toBeInstanceOf(NotFoundError); + expect(err.message).toEqual("BusinessObjects-App responded with Status 404 indicating a requested resource does not exist."); + }); + }); + + describe("on statusCode 501", () => { + + it("should throw NotImplementedError with dto message", async () => { + const err: any = await ensureSuccessResponse(jsonResponse(errorDto, 501)).catch((e: any) => e); + expect(err).toBeInstanceOf(NotImplementedError); + expect(err.message).toContain(errorDto.error.code); + }); + + it("should throw generic NotImplementedError on missing dto", async () => { + const err: any = await ensureSuccessResponse(new Response(null, { status: 501 })).catch((e: any) => e); + expect(err).toBeInstanceOf(NotImplementedError); + expect(err.message).toEqual("BusinessObjects-App responded with Status 501 indicating a requested feature is not implemented."); + }); + }); + + describe("on unknown statusCode", () => { + + it("should throw BusinessObjectsError with dto message", async () => { + const err: any = await ensureSuccessResponse(jsonResponse(errorDto, 500)).catch((e: any) => e); + expect(err).toBeInstanceOf(BusinessObjectsError); + expect(err.message).toContain(errorDto.error.code); + }); + + it("should throw generic BusinessObjectsError on missing dto", async () => { + const err: any = await ensureSuccessResponse(new Response(null, { status: 500 })).catch((e: any) => e); + expect(err).toBeInstanceOf(BusinessObjectsError); + expect(err.message).toEqual("BusinessObjects-App responded with status 500."); + }); + }); +}); diff --git a/packages/business-objects/src/utils/business-objects-error.ts b/packages/business-objects/src/utils/business-objects-error.ts new file mode 100644 index 00000000..e77a64db --- /dev/null +++ b/packages/business-objects/src/utils/business-objects-error.ts @@ -0,0 +1,120 @@ +import { BadInputError, DvelopSdkError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; + +/** + * ErrorDto for BusinessObjects-App. + * @category Error + */ +export interface BusinessObjectsErrorDto { + error: { + /* Service-defined error code - see [BusinessObjects-Error Codes](https://dv-businessobjects-assets.s3.eu-central-1.amazonaws.com/documentation/latest/business_objects_api.html#error-codes) for more information */ + code: string; + /* A human-readable representation of the error. */ + message: string; + /* A collection of error details. */ + details?: { + /* An error detail code defined by the service. */ + code: string; + /* A human-readable representation of the error detail. */ + message: string; + }[]; + /* Debugging information to help determine the error cause. */ + innerError?: { + /* The timestamp as the error occurred. */ + timestamp: string; + /* A randomly generated identifier that uniquely distinguishes each request and that can be used for correlation purposes. */ + requestId: string; + } + } +} + +/** + * Generic Error for business-objects package. + * @category Error + */ +/* istanbul ignore next */ +export class BusinessObjectsError extends DvelopSdkError { + + constructor(public message: string, public originalError?: Error) { + super(message); + Object.setPrototypeOf(this, BusinessObjectsError.prototype); + } +} + +/** + * Indicates that a requested feature is not implemented by the BusinessObjects-App. + * @category Error + */ +/* istanbul ignore next */ +export class NotImplementedError extends DvelopSdkError { + + constructor(public message: string, public originalError?: Error) { + super(message); + Object.setPrototypeOf(this, NotImplementedError.prototype); + } +} + +/** + * Builds a human-readable message from a {@link BusinessObjectsErrorDto}. + * @internal + * @category Error + */ +export function getErrorString(error: BusinessObjectsErrorDto): string | null { + + if (error?.error) { + + let detailString: string = ""; + + if (error.error.details && error.error.details.length > 0) { + detailString = error.error.details + .reduce((detailString, detail) => detailString += `\t * ${detail.message} (${detail.code})\n`, "\n"); + } + + return `${error.error.message} (${error.error.code}).${detailString}`; + } else { + return null; + } +} + +/** + * Throws a typed error if the response indicates failure. + * @internal + * @category Http + */ +export async function ensureSuccessResponse(response: Response): Promise { + + if (response.ok) return; + + let body: any; + try { + body = await response.clone().json(); + } catch { + try { + body = await response.clone().text(); + } catch { + body = undefined; + } + } + + const errorString: string | null = (body && typeof body === "object") ? getErrorString(body) : null; + const bodyString: string | null = typeof body === "string" && body.length > 0 ? body : null; + + switch (response.status) { + case 400: + case 409: // Conflict + case 413: // Request Entity Too Large + case 414: // URI Too Long + case 429: // Too Many Requests + case 431: // Request Header Fields Too Large + throw new BadInputError(errorString ?? "BusinessObjects-App responded with Status 400 indicating bad Request-Parameters."); + case 401: + throw new UnauthorizedError(errorString ?? bodyString ?? "BusinessObjects-App responded with Status 401 indicating bad authSessionId."); + case 403: + throw new ForbiddenError(errorString ?? "BusinessObjects-App responded with Status 403 indicating a forbidden action."); + case 404: + throw new NotFoundError(errorString ?? "BusinessObjects-App responded with Status 404 indicating a requested resource does not exist."); + case 501: + throw new NotImplementedError(errorString ?? "BusinessObjects-App responded with Status 501 indicating a requested feature is not implemented."); + default: + throw new BusinessObjectsError(errorString ?? `BusinessObjects-App responded with status ${response.status}.`); + } +} diff --git a/packages/business-objects/src/utils/http.spec.ts b/packages/business-objects/src/utils/http.spec.ts deleted file mode 100644 index 72d03016..00000000 --- a/packages/business-objects/src/utils/http.spec.ts +++ /dev/null @@ -1,445 +0,0 @@ -import { BadInputError, DvelopContext, DvelopHttpClient, DvelopHttpError, DvelopHttpRequestConfig, DvelopHttpResponse, DvelopSdkError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; -import { _defaultHttpRequestFunctionFactory, BusinessObjectsError, NotImplementedError } from "./http"; - -describe("defaultHttpRequestFunctionFactory", () => { - - let mockRequestFunction = jest.fn(); - let mockHttpClient: DvelopHttpClient = { - request: mockRequestFunction - }; - - let context: DvelopContext; - let config: DvelopHttpRequestConfig; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it("should call request", async () => { - - const response: DvelopHttpResponse = { - data: "HiItsMeResponse" - } as DvelopHttpResponse; - mockRequestFunction.mockResolvedValue(response); - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - const result = await requestFunction(context, config); - - expect(mockRequestFunction).toHaveBeenCalledTimes(1); - expect(result).toBe(response); - }); - - describe("error-handling", () => { - - describe("on statusCode 4XX", () => { - - [400, 409, 413, 414, 429, 431].forEach(status => { - it(`should throw BadInputError on BusinessObjectsErrorDto and status:${status}`, async () => { - - const error: DvelopHttpError = { - response: { - status: status, - data: { - error: { - code: "HiItsMeErrorCode", - message: "HiItsMeErrorMessage", - details: [{ - code: "HiItsMeDetail1Code", - message: "HiItsMeDetail1Message" - }, { - code: "HiItsMeDetail2Code", - message: "HiItsMeDetail2Message" - }] - } - } - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof BadInputError).toBeTruthy(); - expect(expectedError.message).toContain((error.response.data as any).error.code); - expect(expectedError.message).toContain((error.response.data as any).error.message); - expect(expectedError.message).toContain((error.response.data as any).error.details[0].code); - expect(expectedError.message).toContain((error.response.data as any).error.details[0].message); - expect(expectedError.message).toContain((error.response.data as any).error.details[1].code); - expect(expectedError.message).toContain((error.response.data as any).error.details[1].message); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic BadInputError on missing BusinessObjectsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: status - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof BadInputError).toBeTruthy(); - expect(expectedError.message).toEqual("BusinessObjects-App responded with Status 400 indicating bad Request-Parameters. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - }); - }); - - describe("on statusCode 401", () => { - - it("should throw Unauthorized on BusinessObjectsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 401, - data: { - error: { - code: "HiItsMeErrorCode", - message: "HiItsMeErrorMessage", - details: [] - } - } - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof UnauthorizedError).toBeTruthy(); - expect(expectedError.message).toContain((error.response.data as any).error.code); - expect(expectedError.message).toContain((error.response.data as any).error.message); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw Unauthorized on body", async () => { - - const error: DvelopHttpError = { - response: { - status: 401, - data: "HiItsMeErrorReason" - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: UnauthorizedError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof DvelopSdkError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any)); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic BusinessObjectsError on missing Body", async () => { - - const error: DvelopHttpError = { - response: { - status: 401 - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof UnauthorizedError).toBeTruthy(); - expect(expectedError.message).toEqual("BusinessObjects-App responded with Status 401 indicating bad authSessionId."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on statusCode 403", () => { - - it("should throw ForbiddenError on BusinessObjectsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 403, - data: { - error: { - code: "HiItsMeErrorCode", - message: "HiItsMeErrorMessage" - } - } - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof ForbiddenError).toBeTruthy(); - expect(expectedError.message).toContain((error.response.data as any).error.code); - expect(expectedError.message).toContain((error.response.data as any).error.message); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic ForbiddenError on missing BusinessObjectsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 403 - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof ForbiddenError).toBeTruthy(); - expect(expectedError.message).toEqual("BusinessObjects-App responded with Status 403 indicating a forbidden action. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on statusCode 404", () => { - - it("should throw NotFoundError on BusinessObjectsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 404, - data: { - error: { - code: "HiItsMeErrorCode", - message: "HiItsMeErrorMessage" - } - } - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotFoundError).toBeTruthy(); - expect(expectedError.message).toContain((error.response.data as any).error.code); - expect(expectedError.message).toContain((error.response.data as any).error.message); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic BadInputError on missing BusinessObjectsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 404 - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotFoundError).toBeTruthy(); - expect(expectedError.message).toEqual("BusinessObjects-App responded with Status 404 indicating a requested resource does not exist. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on statusCode 501", () => { - - it("should throw NotImplemented on BusinessObjectsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 501, - data: { - error: { - code: "HiItsMeErrorCode", - message: "HiItsMeErrorMessage" - } - } - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotImplementedError).toBeTruthy(); - expect(expectedError.message).toContain((error.response.data as any).error.code); - expect(expectedError.message).toContain((error.response.data as any).error.message); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic NotImplemented on missing BusinessObjectsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 501 - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotImplementedError).toBeTruthy(); - expect(expectedError.message).toEqual("BusinessObjects-App responded with Status 501 indicating a requested feature is not implemented. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on unknown statusCode", () => { - - it("should throw BusinessObjectsError on BusinessObjectsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 500, - data: { - error: { - code: "HiItsMeErrorCode", - message: "HiItsMeErrorMessage" - } - } - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof BusinessObjectsError).toBeTruthy(); - expect(expectedError.message).toContain((error.response.data as any).error.code); - expect(expectedError.message).toContain((error.response.data as any).error.message); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic BusinessObjectsError on missing BusinessObjectsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 500 - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof BusinessObjectsError).toBeTruthy(); - expect(expectedError.message).toEqual(`BusinessObjects-App responded with status ${error.response.status}. See 'originalError'-property for details.`); - expect(expectedError.originalError).toBe(error); - }); - }); - - - - - it("should throw generic BusinessObjectsError on no response", async () => { - const error = new Error("HiItsMeError"); - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof BusinessObjectsError).toBeTruthy(); - expect(expectedError.message).toEqual(`Request to BusinessObjects-App failed: ${error.message}. See 'originalError'-property for details.`); - expect(expectedError.originalError).toBe(error); - }); - - - }); - -}); \ No newline at end of file diff --git a/packages/business-objects/src/utils/http.ts b/packages/business-objects/src/utils/http.ts deleted file mode 100644 index 8f161592..00000000 --- a/packages/business-objects/src/utils/http.ts +++ /dev/null @@ -1,130 +0,0 @@ - -import { DvelopContext, DvelopHttpRequestConfig, DvelopHttpResponse, DvelopHttpClient, defaultDvelopHttpClientFactory, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError, DvelopSdkError } from "@dvelop-sdk/core"; -export { DvelopHttpRequestConfig as HttpConfig, DvelopHttpResponse as HttpResponse } from "@dvelop-sdk/core"; - -/** - * ErrorDto for BusinessObjects-App. - * @category Error - */ -export interface BusinessObjectsErrorDto { - error: { - /* Service-defined error code - see [BusinessObjects-Error Codes](https://dv-businessobjects-assets.s3.eu-central-1.amazonaws.com/documentation/latest/business_objects_api.html#error-codes) for more information */ - code: string; - /* A human-readable representation of the error. */ - message: string; - /* A collection of error details. */ - details: { - /* An error detail code defined by the service. */ - code: string; - /* A human-readable representation of the error detail. */ - message: string; - }[]; - /* Debugging information to help determine the error cause. */ - innerError: { - /* The timestamp as the error occurred. */ - timestamp: string; - /* A randomly generated identifier that uniquely distinguishes each request and that can be used for correlation purposes. */ - requestId: string; - } - } -} - -function getErrorString(error: BusinessObjectsErrorDto): string | null { - - - if (error?.error) { - - let detailString: string = ""; - - if (error.error.details && error.error.details.length > 0) { - detailString = error.error.details - .reduce((detailString, detail) => detailString += `\t * ${detail.message} (${detail.code})\n`, "\n"); - } - - return `${error.error.message} (${error.error.code}).${detailString}\nSee 'See 'originalError'-property for details.'`; - } else { - return null; - } -} - -/** -* Generic Error for business-objects package. -* @category Error -*/ -/* istanbul ignore next */ -export class BusinessObjectsError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars - constructor(public message: string, public originalError?: Error) { - super(message); - Object.setPrototypeOf(this, BusinessObjectsError.prototype); - } -} - -/** -* TODO: Generic Error for business-objects package. -* @category Error -*/ -/* istanbul ignore next */ -export class NotImplementedError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars - constructor(public message: string, public originalError?: Error) { - super(message); - Object.setPrototypeOf(this, NotImplementedError.prototype); - } -} - -/** - * Factory used to create the default httpRequestFunction. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @internal - * @category Http - */ -export function _defaultHttpRequestFunctionFactory(httpClient: DvelopHttpClient): (context: DvelopContext, config: DvelopHttpRequestConfig) => Promise { - return async (context: DvelopContext, config: DvelopHttpRequestConfig) => { - - try { - return await httpClient.request(context, config); - } catch (error: any) { - - if (error.response) { - - switch (error.response.status) { - case 400: - case 409: // Conflict - case 413: // Request Entity Too Large - case 414: // URI Too Long - case 429: // Too Many Requests - case 431: // Request Header Fields Too Large - throw new BadInputError(getErrorString(error.response.data) || "BusinessObjects-App responded with Status 400 indicating bad Request-Parameters. See 'originalError'-property for details.", error); - - case 401: - throw new UnauthorizedError(getErrorString(error.response.data) || error.response.data || "BusinessObjects-App responded with Status 401 indicating bad authSessionId.", error); - - case 403: - throw new ForbiddenError(getErrorString(error.response.data) || "BusinessObjects-App responded with Status 403 indicating a forbidden action. See 'originalError'-property for details.", error); - - case 404: - throw new NotFoundError(getErrorString(error.response.data) || "BusinessObjects-App responded with Status 404 indicating a requested resource does not exist. See 'originalError'-property for details.", error); - - case 501: - throw new NotImplementedError(getErrorString(error.response.data) || "BusinessObjects-App responded with Status 501 indicating a requested feature is not implemented. See 'originalError'-property for details.", error); - - default: - throw new BusinessObjectsError(getErrorString(error.response.data) || `BusinessObjects-App responded with status ${error.response.status}. See 'originalError'-property for details.`, error); - } - } else { - throw new BusinessObjectsError(`Request to BusinessObjects-App failed: ${error.message}. See 'originalError'-property for details.`, error); - } - } - }; -} - - -/** - * Default httpRequestFunction used in dms-package. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @internal - * @category Http - */ -/* istanbul ignore next */ -export async function _defaultHttpRequestFunction(context: DvelopContext, config: DvelopHttpRequestConfig): Promise { - return _defaultHttpRequestFunctionFactory(defaultDvelopHttpClientFactory())(context, config); -} \ No newline at end of file diff --git a/packages/business-objects/tsconfig.build.json b/packages/business-objects/tsconfig.build.json new file mode 100644 index 00000000..0c9b67d5 --- /dev/null +++ b/packages/business-objects/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig-base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.spec.ts", "src/**/tmp-*.ts"] +} diff --git a/packages/business-objects/tsconfig.json b/packages/business-objects/tsconfig.json index 1e5dc209..3912f472 100644 --- a/packages/business-objects/tsconfig.json +++ b/packages/business-objects/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "outDir": "lib", /* Redirect output structure to the directory. */ - "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "noEmit": true, + "composite": false, + "declaration": false, + "declarationMap": false }, - "include": ["src/**/*"], - "exclude": ["src/**/*.spec.ts"] -} \ No newline at end of file + "include": ["src/**/*"] +} diff --git a/packages/core/package.json b/packages/core/package.json index 99207abc..1dbae260 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@dvelop-sdk/core", "description": "This package contains shared functionality for the @dvelop-sdk packages.", - "version": "2.2.3", + "version": "3.0.0", "license": "Apache-2.0", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,9 @@ "access": "public" }, "dependencies": { - "axios": "^0.31.1", - "lodash.merge": "^4.6.2", - "uuid": "^8.3.2" + "lodash.merge": "^4.6.2" }, "devDependencies": { - "@types/lodash.merge": "^4.6.7", - "@types/uuid": "^8.3.1" + "@types/lodash.merge": "^4.6.7" } } diff --git a/packages/core/src/errors/errors.ts b/packages/core/src/errors/errors.ts index c22c7d88..bff63a61 100644 --- a/packages/core/src/errors/errors.ts +++ b/packages/core/src/errors/errors.ts @@ -3,7 +3,7 @@ * @category Error */ export class DvelopSdkError extends Error { - // eslint-disable-next-line no-unused-vars + constructor(public message: string, public originalError?: Error) { super(message); Object.setPrototypeOf(this, DvelopSdkError.prototype); @@ -16,7 +16,7 @@ export class DvelopSdkError extends Error { */ /* istanbul ignore next */ export class BadInputError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars + constructor(public message: string, public originalError?: Error) { super(message); Object.setPrototypeOf(this, BadInputError.prototype); @@ -29,7 +29,7 @@ export class BadInputError extends DvelopSdkError { */ /* istanbul ignore next */ export class UnauthorizedError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars + constructor(public message: string, public originalError?: Error) { super(message); Object.setPrototypeOf(this, UnauthorizedError.prototype); @@ -42,7 +42,7 @@ export class UnauthorizedError extends DvelopSdkError { */ /* istanbul ignore next */ export class ForbiddenError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars + constructor(public message: string, public originalError?: Error) { super(message); Object.setPrototypeOf(this, ForbiddenError.prototype); @@ -55,7 +55,7 @@ export class ForbiddenError extends DvelopSdkError { */ /* istanbul ignore next */ export class NotFoundError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars + constructor(public message: string, public originalError?: Error) { super(message); Object.setPrototypeOf(this, NotFoundError.prototype); diff --git a/packages/core/src/generate-uuid/generate-uudi-id.ts b/packages/core/src/generate-uuid/generate-uudi-id.ts index bfdce555..a2788d71 100644 --- a/packages/core/src/generate-uuid/generate-uudi-id.ts +++ b/packages/core/src/generate-uuid/generate-uudi-id.ts @@ -1,5 +1,3 @@ -import { v4 } from "uuid"; - /** * Generate a unique Version 4 UUID. * @@ -12,7 +10,7 @@ import { v4 } from "uuid"; * @category Core */ export function generateUuid(): string { - return v4(); + return crypto.randomUUID(); } /** diff --git a/packages/core/src/generate-uuid/generate-uuid-id.spec.ts b/packages/core/src/generate-uuid/generate-uuid-id.spec.ts index 1df15818..521e045e 100644 --- a/packages/core/src/generate-uuid/generate-uuid-id.spec.ts +++ b/packages/core/src/generate-uuid/generate-uuid-id.spec.ts @@ -1,40 +1,21 @@ -import * as uuid from "uuid"; import { generateRequestId, generateUuid } from "./generate-uudi-id"; -jest.mock("uuid"); - describe("generateUuid", () => { - const mockedUuid = uuid as jest.Mocked; - - beforeEach(() => { - mockedUuid.v4.mockReset(); - }); - - it("should return UUID from v4", () => { - const uuid = "HiItsMeUuid"; - - mockedUuid.v4.mockImplementation(() => uuid); + it("should return UUID from crypto.randomUUID", () => { + const expected = "ac25ae73-f4b6-477e-a5fa-877c8dea863d"; + jest.spyOn(crypto, "randomUUID").mockReturnValueOnce(expected as `${string}-${string}-${string}-${string}-${string}`); - const result = generateUuid(); - expect(result).toEqual(uuid); + expect(generateUuid()).toEqual(expected); }); }); describe("generateRequestId", () => { - const mockedUuid = uuid as jest.Mocked; + it("should return UUID from crypto.randomUUID", () => { + const expected = "ac25ae73-f4b6-477e-a5fa-877c8dea863d"; + jest.spyOn(crypto, "randomUUID").mockReturnValueOnce(expected as `${string}-${string}-${string}-${string}-${string}`); - beforeEach(() => { - mockedUuid.v4.mockReset(); + expect(generateRequestId()).toEqual(expected); }); - - it("should return UUID from v4", () => { - const uuid = "HiItsMeUuid"; - - mockedUuid.v4.mockImplementation(() => uuid); - - const result = generateRequestId(); - expect(result).toEqual(uuid); - }); -}); \ No newline at end of file +}); diff --git a/packages/core/src/http/axios-follow-hal-json.spec.ts b/packages/core/src/http/axios-follow-hal-json.spec.ts deleted file mode 100644 index 8b2d8f99..00000000 --- a/packages/core/src/http/axios-follow-hal-json.spec.ts +++ /dev/null @@ -1,202 +0,0 @@ - -import { axiosFollowHalJsonFunctionFactory } from "./axios-follow-hal-json"; -import { AxiosInstance, AxiosResponse } from "axios"; -import { DvelopHttpRequestConfig } from "./http-client"; -import { NotFoundError } from ".."; - -describe("axiosFollowHalJsonFunctionFactory", () => { - - let mockRequest = jest.fn(); - let mockAxiosInstance = { - request: mockRequest - } as unknown as AxiosInstance; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - [ - { testContext: "on one call", call: axiosFollowHalJsonFunctionFactory(mockAxiosInstance) }, - { - testContext: "on multiple calls", call: async (config) => { - const c1 = await axiosFollowHalJsonFunctionFactory(mockAxiosInstance)(config); - const c2 = await axiosFollowHalJsonFunctionFactory(mockAxiosInstance)(c1); - return await axiosFollowHalJsonFunctionFactory(mockAxiosInstance)(c2); - } - } - ].forEach(test => { - describe(`followHalJson on ${test.testContext}`, () => { - - [ - { follows: null, templates: null }, - { follows: [], templates: null }, - { follows: null, templates: {} }, - { follows: [], templates: {} }, - ].forEach(testCase => { - it("should do nothing on empty follows and empty templates", async () => { - const result = await test.call(testCase); - expect(result).toBe(testCase); - expect(mockAxiosInstance.request).toHaveBeenCalledTimes(0); - }); - }); - - describe("on follows", () => { - - it("should follow multiple links", async () => { - - const finalUrl: string = "HiItsMeFinalUrl"; - const config: DvelopHttpRequestConfig = { - baseURL: "HiItsMeBaseUrl", - url: "base", - follows: ["1", "2", "3", "final"] - }; - - mockRequest.mockImplementation(config => { - switch (config.url) { - case "base": - return Promise.resolve({ data: { _links: { "1": { href: "one" } } } }); - case "one": - return Promise.resolve({ data: { _links: { "2": { href: "two" } } } }); - case "two": - return Promise.resolve({ data: { _links: { "3": { href: "three" } } } }); - case "three": - return Promise.resolve({ data: { _links: { "final": { href: finalUrl } } } }); - default: - return Promise.reject(new Error()); - } - }); - - const resultConfig: DvelopHttpRequestConfig = await test.call(config); - - expect(resultConfig.url).toEqual(finalUrl); - expect(resultConfig.follows).toBeUndefined(); - expect(mockRequest).toBeCalledTimes(4); - }); - - it("should make internal requests with GET and Accept-Header but return original config", async () => { - const baseURL: string = "HiItsMeBaseUrl"; - const headers: any = { someHeader: "HiItsMeHeader" }; - const finalUrl: string = "HiItsMeFinalUrl"; - const config: DvelopHttpRequestConfig = { - baseURL, - headers, - method: "POST", - url: "base", - follows: ["final"] - }; - - mockRequest.mockResolvedValue({ data: { _links: { "final": { href: finalUrl } } } }); - const resultConfig: DvelopHttpRequestConfig = await test.call(config); - - expect(resultConfig).toHaveProperty("baseURL", baseURL); - expect(resultConfig).toHaveProperty("headers", headers); - expect(resultConfig).toHaveProperty("method", "POST"); - expect(resultConfig).toHaveProperty("url", finalUrl); - - expect(mockRequest).toBeCalledWith(expect.objectContaining({ - baseURL: "HiItsMeBaseUrl", - method: "GET", - headers: expect.objectContaining({ "Accept": "application/hal+json, application/json" }) - })); - }); - - [ - { data: { _links: {} } }, - { data: { _links: { follow: "hi" } } } - ].forEach(testCase => { - it("throw error if no link for follow in _links is given", async () => { - - const config: DvelopHttpRequestConfig = { - baseURL: "HiItsMeBaseUrl", - url: "base", - follows: ["follow"] - }; - - mockRequest.mockResolvedValue(testCase); - let expectedError: NotFoundError; - - try { - await test.call(config); - } catch (e) { - expectedError = e; - } - expect(expectedError instanceof NotFoundError).toBeTruthy(); - expect(expectedError.message).toContain("No hal-json link found for"); - expect(expectedError.message).toContain("follow"); - }); - }); - }); - - describe("on templates", () => { - - [ - { url: "No template", templates: {}, expectedUrl: "No template" }, - { url: "No template", templates: { "unneeded": "hi" }, expectedUrl: "No template" }, - { url: "{test}", templates: { "test": "hi" }, expectedUrl: "hi" }, - { url: "{test1}{test2}", templates: { "test1": "hi", "test2": "ho" }, expectedUrl: "hiho" }, - { url: "{test1} {test2}", templates: { "test1": "hi", "test2": "ho" }, expectedUrl: "hi ho" }, - { url: "{test1}{test2}", templates: { "test1": "hi", "test2": "ho" }, expectedUrl: "hiho" }, - { url: "{test1}/{test2}", templates: { "test1": "hi", "test2": "ho" }, expectedUrl: "hi/ho" }, - { url: "test{test1}/{test2}", templates: { "test1": "hi", "test2": "ho" }, expectedUrl: "testhi/ho" }, - { url: "{test1}/{test2}test", templates: { "test1": "hi", "test2": "ho" }, expectedUrl: "hi/hotest" }, - { url: "!@#$%^^&*()_+{!!}", templates: { "!!": "hi" }, expectedUrl: "!@#$%^^&*()_+hi" }, - { url: "{test}", templates: { "test": "hi", "unneeded": "ho" }, expectedUrl: "hi" }, - - { url: "{?test}", templates: { "test": "hi", "unneeded": "ho" }, expectedUrl: "", expectedParams: { "test": "hi" } }, - { url: "{?test1,test2}", templates: { "test1": "hi", "test2": "ho" }, expectedUrl: "", expectedParams: { "test1": "hi", "test2": "ho" } }, - { url: "{?test1,test2}", templates: { "test1": "hi", "unneeded": "ho" }, expectedUrl: "", expectedParams: { "test1": "hi" } }, - { url: "test{?test}test", templates: { "test": "hi", "unneeded": "ho" }, expectedUrl: "testtest", expectedParams: { "test": "hi" } }, - { url: "{?test1,test2}test", templates: { "test1": "hi", "test2": "ho" }, expectedUrl: "test", expectedParams: { "test1": "hi", "test2": "ho" } }, - { url: "test{?test1,test2}", templates: { "test1": "hi", "unneeded": "ho" }, expectedUrl: "test", expectedParams: { "test1": "hi" } }, - { url: "/test{?test1,test2}", templates: { "unneeded": "hi", "test2": "ho" }, expectedUrl: "/test", expectedParams: { "test2": "ho" } }, - { url: "/test{?}", templates: { "unneeded": "hi" }, expectedUrl: "/test", expectedParams: {} }, - { url: "/test{?test1}", templates: { "test1": [] }, expectedUrl: "/test", expectedParams: {} }, - { url: "/test{?test1}", templates: { "test1": ["hi"] }, expectedUrl: "/test", expectedParams: { "test1": "[\"hi\"]" } }, - { url: "/test{?test1}", templates: { "test1": ["hi", "ho"] }, expectedUrl: "/test", expectedParams: { "test1": "[\"hi\",\"ho\"]" } }, - { url: "/test{?test1,test2}", templates: { "test1": ["hi", "ho"], "test2": ["1", "2"] }, expectedUrl: "/test", expectedParams: { "test1": "[\"hi\",\"ho\"]", "test2": "[\"1\",\"2\"]" } }, - { url: "/test{?test1,test2}", templates: { "test1": { "hi": "ho" }, "test2": { "1": "2" } }, expectedUrl: "/test", expectedParams: { "test1": "{\"hi\":\"ho\"}", "test2": "{\"1\":\"2\"}" } }, - - { url: "a{B}c{D}e", templates: { D: "d" }, expectedUrl: "acde" }, - { url: "a{B}c{D}e", templates: {}, expectedUrl: "ace" }, - { url: "a{B}c{D}e", templates: null, expectedUrl: "ace" }, - { url: "a{B}c{D}e", templates: undefined, expectedUrl: "ace" }, - { url: "a{B}c{D}e", expectedUrl: "ace" }, - { url: "a{B}c{D}e", templates: { B: "b" }, expectedUrl: "abce" } - ].forEach(testCase => { - it(`should replace templates in "${testCase.url}" to "${testCase.expectedUrl}" for initial templating"`, async () => { - - const result: DvelopHttpRequestConfig = await test.call({ - url: testCase.url, - templates: testCase.templates - }); - - expect(result.url).toEqual(testCase.expectedUrl); - expect(result.params).toEqual(testCase.expectedParams || {}); - }); - - it(`should replace templates in "${testCase.url}" to "${testCase.expectedUrl} for response templating"`, async () => { - - const response: AxiosResponse = { - data: { - _links: { - follow: { - href: testCase.url - } - } - } - } as AxiosResponse; - - mockRequest.mockResolvedValue(response); - - const result: DvelopHttpRequestConfig = await test.call({ - follows: ["follow"], - templates: testCase.templates - }); - expect(result.url).toEqual(testCase.expectedUrl); - expect(result.params).toEqual(testCase.expectedParams || {}); - }); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/packages/core/src/http/axios-follow-hal-json.ts b/packages/core/src/http/axios-follow-hal-json.ts deleted file mode 100644 index fc2ac048..00000000 --- a/packages/core/src/http/axios-follow-hal-json.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { AxiosInstance } from "axios"; -import { DvelopHttpResponse, NotFoundError } from ".."; -import { DvelopHttpRequestConfig } from "./http-client"; - -export interface HalJsonResponse { - _links: { - [key: string]: { - href: string; - } - } -} - -interface UrlParamSplit { - url: string; - params: { [key: string]: any } -} - -async function getFollowUrl(axios: AxiosInstance, config: DvelopHttpRequestConfig, follow: string): Promise { - - if (!config.headers) { - config.headers = {}; - } - - config.method = "GET"; - config.headers["Accept"] = "application/hal+json, application/json"; - config.responseType = "json"; - config.follows = []; - - let response: DvelopHttpResponse; - - response = await axios.request(config); - - if (!response.data._links[follow] || !response.data._links[follow].href) { - throw new NotFoundError(`No hal-json link found for ${follow}.`); - } - - let followUrl: string = response.data._links[follow].href; - - return templateUrl(followUrl, config.params, config.templates); -} - -function templateUrl(url: string, originalParams: { [key: string]: string | undefined }, templates: { [key: string]: string } | undefined): { url: string, params: { [key: string]: string } } { - - let matchArray: RegExpExecArray | null; - - let params: { [key: string]: string } = originalParams ? originalParams as { [key: string]: string } : {}; - - while ((matchArray = /{(.*?)}/g.exec(url)) !== null) { - - const matchWithBrackets: string = matchArray[0]; - const matchWithoutBrackets: string = matchArray[1]; - - // find matches like {?a,b,c} - if (matchWithoutBrackets.match(/^\?.*/)) { - - const keys: string[] = matchWithoutBrackets.slice(1).split(","); - - keys.forEach(key => { - - if (templates && templates[key]) { - - let value: any = templates[key]; - - if (typeof value === "object" && !Array.isArray(value)) { - value = JSON.stringify(value); - } - - if (Array.isArray(value)) { - if (value.length > 0) { - value = JSON.stringify(templates[key]); - params[key] = value; - } - } else { - params[key] = value; - } - } - }); - - url = url.replace(matchWithBrackets, ""); - - } else { - - if (templates && templates[matchWithoutBrackets]) { - url = url.replace(matchWithBrackets, templates[matchWithoutBrackets]); - } else { - url = url.replace(matchWithBrackets, ""); - } - } - } - - return { url, params }; -} - -export function axiosFollowHalJsonFunctionFactory(axios: AxiosInstance): (config: DvelopHttpRequestConfig) => Promise { - return async (config: DvelopHttpRequestConfig) => { - if (config.url) { - const urlAndParams: UrlParamSplit = templateUrl(config.url, config.params, config.templates); - config.url = urlAndParams.url; - config.params = urlAndParams.params; - } - - if (!config.follows || config.follows.length === 0) { - return config; - } - - for (let f of config.follows) { - const follow: any = await getFollowUrl(axios, { ...config }, f); - config.url = follow.url; - config.params = follow.params; - } - - delete config.follows; - return config; - }; -} \ No newline at end of file diff --git a/packages/core/src/http/fetch.spec.ts b/packages/core/src/http/fetch.spec.ts new file mode 100644 index 00000000..4b7e5d15 --- /dev/null +++ b/packages/core/src/http/fetch.spec.ts @@ -0,0 +1,140 @@ +/// +import { DvelopContext } from "../context/context"; +import { generateRequestId } from "../generate-uuid/generate-uudi-id"; +import { DvelopOptions } from "../options/options"; +import { TraceContext } from "../trace-context/trace-context"; +import { buildTraceparentHeader } from "../trace-context/traceparent-header/traceparent-header"; +import { dvelopFetch } from "./fetch"; + +jest.mock("../generate-uuid/generate-uudi-id"); +const mockGenerateRequestId = generateRequestId as jest.MockedFunction; + +jest.mock("../trace-context/traceparent-header/traceparent-header"); +const mockBuildTraceparentHeader = buildTraceparentHeader as jest.MockedFunction; + +describe("dvelopFetch", () => { + + let mockFetch: jest.SpyInstance; + let mockResponse: Response; + + beforeEach(() => { + jest.resetAllMocks(); + mockResponse = new Response(); + mockFetch = jest.spyOn(globalThis, "fetch").mockResolvedValue(mockResponse); + mockGenerateRequestId.mockReturnValue("generated-request-id"); + mockBuildTraceparentHeader.mockReturnValue("00-traceid-spanid-01"); + }); + + describe("URL construction", () => { + + it("should call fetch with systemBaseUri concatenated with path", async () => { + const context: DvelopContext = { systemBaseUri: "https://example.d-velop.cloud" }; + await dvelopFetch(context, "/some/path", {}); + expect(mockFetch).toHaveBeenCalledWith("https://example.d-velop.cloud/some/path", expect.anything()); + }); + }); + + describe("default headers", () => { + + it("should set Content-Type to application/json", async () => { + await dvelopFetch({}, "/path", {}); + const headers = mockFetch.mock.calls[0][1].headers as Record; + expect(headers["Content-Type"]).toBe("application/json"); + }); + + it("should set Authorization with authSessionId as Bearer token", async () => { + const context: DvelopContext = { authSessionId: "my-session-id" }; + await dvelopFetch(context, "/path", {}); + const headers = mockFetch.mock.calls[0][1].headers as Record; + expect(headers["Authorization"]).toBe("Bearer my-session-id"); + }); + + it("should set x-dv-request-id from context when provided", async () => { + const context: DvelopContext = { requestId: "context-request-id" }; + await dvelopFetch(context, "/path", {}); + const headers = mockFetch.mock.calls[0][1].headers as Record; + expect(headers["x-dv-request-id"]).toBe("context-request-id"); + expect(mockGenerateRequestId).not.toHaveBeenCalled(); + }); + + it("should generate x-dv-request-id when context provides no requestId", async () => { + await dvelopFetch({}, "/path", {}); + expect(mockGenerateRequestId).toHaveBeenCalledTimes(1); + const headers = mockFetch.mock.calls[0][1].headers as Record; + expect(headers["x-dv-request-id"]).toBe("generated-request-id"); + }); + + it("should build and set traceparent header when traceContext is provided", async () => { + const traceContext: TraceContext = { traceId: "trace", spanId: "span", version: 0, sampled: true }; + const context: DvelopContext = { traceContext }; + await dvelopFetch(context, "/path", {}); + expect(mockBuildTraceparentHeader).toHaveBeenCalledWith(traceContext); + const headers = mockFetch.mock.calls[0][1].headers as Record; + expect(headers["traceparent"]).toBe("00-traceid-spanid-01"); + }); + + it("should set traceparent to empty string when no traceContext provided", async () => { + await dvelopFetch({}, "/path", {}); + expect(mockBuildTraceparentHeader).not.toHaveBeenCalled(); + const headers = mockFetch.mock.calls[0][1].headers as Record; + expect(headers["traceparent"]).toBe(""); + }); + }); + + describe("init merging", () => { + + it("should include provided init properties in request", async () => { + await dvelopFetch({}, "/path", { method: "POST", body: "payload" }); + const init = mockFetch.mock.calls[0][1]; + expect(init.method).toBe("POST"); + expect(init.body).toBe("payload"); + }); + + it("should merge initOverwrite headers from options over defaults", async () => { + const options: DvelopOptions = { + initOverwrite: { headers: { "Content-Type": "text/plain", "X-Custom": "value" } } + }; + await dvelopFetch({}, "/path", {}, options); + const headers = mockFetch.mock.calls[0][1].headers as Record; + expect(headers["Content-Type"]).toBe("text/plain"); + expect(headers["X-Custom"]).toBe("value"); + }); + + it("should use empty initOverwrite when options provides none", async () => { + const options: DvelopOptions = { onResponse: jest.fn().mockResolvedValue(mockResponse) }; + await dvelopFetch({}, "/path", {}, options); + const headers = mockFetch.mock.calls[0][1].headers as Record; + expect(headers["Content-Type"]).toBe("application/json"); + }); + }); + + describe("response handling", () => { + + it("should return raw Response when no options given", async () => { + const result = await dvelopFetch({}, "/path", {}); + expect(result).toBe(mockResponse); + }); + + it("should return raw Response when options has no onResponse", async () => { + const options: DvelopOptions = { initOverwrite: {} }; + const result = await dvelopFetch({}, "/path", {}, options); + expect(result).toBe(mockResponse); + }); + + it("should call onResponse with the fetch response and return its result", async () => { + const parsedData = { id: 42, name: "test" }; + const onResponse = jest.fn().mockResolvedValue(parsedData); + const options: DvelopOptions = { onResponse }; + const result = await dvelopFetch({}, "/path", {}, options); + expect(onResponse).toHaveBeenCalledWith(mockResponse); + expect(result).toEqual(parsedData); + }); + + it("should support synchronous onResponse callback", async () => { + const parsedData = "sync-result"; + const options: DvelopOptions = { onResponse: jest.fn().mockReturnValue(parsedData) }; + const result = await dvelopFetch({}, "/path", {}, options); + expect(result).toBe(parsedData); + }); + }); +}); diff --git a/packages/core/src/http/fetch.ts b/packages/core/src/http/fetch.ts new file mode 100644 index 00000000..4cfc17f0 --- /dev/null +++ b/packages/core/src/http/fetch.ts @@ -0,0 +1,29 @@ +import { DvelopContext } from "../context/context.js"; +import { generateRequestId } from "../generate-uuid/generate-uudi-id.js"; +import { DvelopOptions } from "../options/options.js"; +import { buildTraceparentHeader } from "../trace-context/traceparent-header/traceparent-header.js"; +import { deepMergeObjects } from "../util/deep-merge-objects.js"; + +export async function dvelopFetch(context: DvelopContext, path: string, init: RequestInit): Promise; +export async function dvelopFetch(context: DvelopContext, path: string, init: RequestInit, options: DvelopOptions): Promise; +export async function dvelopFetch( + context: DvelopContext, + path: string, + init: RequestInit, + options?: DvelopOptions +): Promise { + + const defaultInit: Partial = { + headers: { + "Content-Type": "application/json", + "Accept": "application/hal+json, application/json", + "Authorization": `Bearer ${context.authSessionId}`, + "x-dv-request-id": context.requestId ?? generateRequestId(), + "traceparent": context.traceContext ? buildTraceparentHeader(context.traceContext) : "" + } + }; + + const finalInit = deepMergeObjects>(defaultInit, init, options?.initOverwrite ?? {}); + const response = await fetch(`${context.systemBaseUri}${path}`, finalInit); + return (options?.onResponse) ? await options.onResponse(response) : response; +}; \ No newline at end of file diff --git a/packages/core/src/http/http-client.spec.ts b/packages/core/src/http/http-client.spec.ts deleted file mode 100644 index 118f05c8..00000000 --- a/packages/core/src/http/http-client.spec.ts +++ /dev/null @@ -1,187 +0,0 @@ -import axios, { AxiosInstance } from "axios"; -import { DVELOP_REQUEST_ID_HEADER, TRACEPARENT_HEADER } from "../http/http-headers"; -import { DvelopContext } from "../context/context"; -import { axiosFollowHalJsonFunctionFactory } from "./axios-follow-hal-json"; -import { DvelopHttpClient, DvelopHttpRequestConfig, axiosHttpClientFactory, axiosInstanceFactory } from "./http-client"; -import { TraceContext } from "../trace-context/trace-context"; - -jest.mock("axios"); -const mockAxios = axios as jest.Mocked; - -jest.mock("./axios-follow-hal-json"); -const mockAxiosFollowHalJsonFunctionFactory = axiosFollowHalJsonFunctionFactory as jest.Mocked; - -describe("axiosInstanceFactory", () => { - it("should correctly initialize AxiosInstance", () => { - - let mockAxiosInstance: AxiosInstance = { - interceptors: { - request: { - use: jest.fn() - } - } - } as unknown as AxiosInstance; - - let mockAxiosFollowHalJson = jest.fn(); - - (mockAxiosFollowHalJsonFunctionFactory as jest.Mock).mockReturnValue(mockAxiosFollowHalJson); - mockAxios.create.mockReturnValue(mockAxiosInstance); - - - const result: AxiosInstance = axiosInstanceFactory(mockAxios); - - expect(mockAxios.create).toHaveBeenCalledTimes(1); - expect(mockAxiosFollowHalJsonFunctionFactory).toHaveBeenCalledWith(mockAxiosInstance); - expect(mockAxiosInstance.interceptors.request.use).toHaveBeenCalledWith(mockAxiosFollowHalJson); - expect(result).toBe(mockAxiosInstance); - }); -}); - -describe("axiosHttpClientFactory", () => { - - let mockAxiosInstance: AxiosInstance; - let mockGenerateRequestId = jest.fn(); - let mockBuildTraceparentHeader = jest.fn(); - let mockMergeConfigs = jest.fn(); - - beforeEach(() => { - jest.resetAllMocks(); - - mockMergeConfigs.mockReturnValue({}); - - mockAxiosInstance = { - request: jest.fn() - } as unknown as AxiosInstance; - }); - - - let context: DvelopContext; - let config: DvelopHttpRequestConfig; - let axiosClient: DvelopHttpClient; - - beforeEach(() => { - axiosClient = axiosHttpClientFactory(mockAxiosInstance, mockGenerateRequestId, mockBuildTraceparentHeader, mockMergeConfigs); - }); - - it("should have default config on empty context", async () => { - - context = {}; - - await axiosClient.request(context, {}); - - expect(mockMergeConfigs).toHaveBeenCalledWith({ - headers: { - "ContentType": "application/json", - "Accept": "application/hal+json, application/json" - } - }, {}); - }); - - it("should set systemBaseUri as baseURL", async () => { - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - - await axiosClient.request(context, {}); - - expect(mockMergeConfigs).toHaveBeenCalledWith(expect.objectContaining({ - baseURL: context.systemBaseUri - }), {}); - }); - - it("should set authSessionId as Bearer-Token", async () => { - - context = { - authSessionId: "HiItsMeAuthSessionId" - }; - - await axiosClient.request(context, {}); - - expect(mockMergeConfigs).toHaveBeenCalledWith(expect.objectContaining({ - headers: expect.objectContaining({ - "Authorization": `Bearer ${context.authSessionId}` - }) - }), {}); - }); - - it("should set requestId as header", async () => { - - context = { - requestId: "HiItsMeRequestId" - }; - - await axiosClient.request(context, {}); - - expect(mockMergeConfigs).toHaveBeenCalledWith(expect.objectContaining({ - headers: expect.objectContaining({ - [DVELOP_REQUEST_ID_HEADER]: context.requestId - }) - }), {}); - }); - - it("should generate requestId if non given", async () => { - - context = {}; - const requestId: string = "HiItsMeRequestId"; - mockGenerateRequestId.mockReturnValue(requestId); - - await axiosClient.request(context, {}); - - expect(mockGenerateRequestId).toHaveBeenCalledTimes(1); - expect(mockMergeConfigs).toHaveBeenCalledWith(expect.objectContaining({ - headers: expect.objectContaining({ - [DVELOP_REQUEST_ID_HEADER]: requestId - }) - }), {}); - }); - - it("should build traceparent-Header on traceContext", async () => { - - const traceContext: TraceContext = { traceId: "trace", spanId: "span", version: 0, sampled: true }; - const traceParentHeader: string = "someTraceParentHeader"; - context = { - traceContext: traceContext - }; - mockBuildTraceparentHeader.mockReturnValue(traceParentHeader); - - await axiosClient.request(context, {}); - - expect(mockBuildTraceparentHeader).toHaveBeenCalledTimes(1); - expect(mockBuildTraceparentHeader).toHaveBeenCalledWith(traceContext); - - expect(mockMergeConfigs).toHaveBeenCalledWith(expect.objectContaining({ - headers: expect.objectContaining({ - [TRACEPARENT_HEADER]: traceParentHeader - }) - }), {}); - }); - - it("should merge configs", async () => { - - config = { - method: "GET", - data: "HiItsMeData" - }; - - await axiosClient.request({}, config); - - expect(mockMergeConfigs).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ - method: config.method, - data: config.data - })); - }); - - it("should fire request with merged config", async () => { - - const mergedConfig = { - hi: "itsMeTest" - }; - - mockMergeConfigs.mockReturnValue(mergedConfig); - - await axiosClient.request({}, config); - expect(mockAxiosInstance.request).toHaveBeenCalledTimes(1); - expect(mockAxiosInstance.request).toHaveBeenCalledWith(mergedConfig); - }); -}); \ No newline at end of file diff --git a/packages/core/src/http/http-client.ts b/packages/core/src/http/http-client.ts deleted file mode 100644 index d97c8cb4..00000000 --- a/packages/core/src/http/http-client.ts +++ /dev/null @@ -1,72 +0,0 @@ -import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosStatic } from "axios"; -import { generateRequestId } from ".."; -import { DvelopContext } from "../context/context"; -import { axiosFollowHalJsonFunctionFactory } from "./axios-follow-hal-json"; -import { DVELOP_REQUEST_ID_HEADER, TRACEPARENT_HEADER } from "./http-headers"; -import { TraceContext } from "../trace-context/trace-context"; -import { buildTraceparentHeader } from "../trace-context/traceparent-header/traceparent-header"; -import { deepMergeObjects } from "../util/deep-merge-objects"; - -export interface DvelopHttpRequestConfig extends AxiosRequestConfig { - follows?: string[]; - templates?: { [key: string]: any } -} - -export interface DvelopHttpResponse extends AxiosResponse { } - -export interface DvelopHttpError extends AxiosError { } - -export interface DvelopHttpClient { - request(context: DvelopContext, config: DvelopHttpRequestConfig): Promise -} - -export function axiosInstanceFactory(axios: AxiosStatic): AxiosInstance { - const instance: AxiosInstance = axios.create(); - instance.interceptors.request.use(axiosFollowHalJsonFunctionFactory(instance)); - return instance; -} - -export function axiosHttpClientFactory( - axiosInstance: AxiosInstance, - generateRequestId: () => string, - buildTraceparentHeader: (traceContext: TraceContext) => string, - mergeConfigs: (...configs: DvelopHttpRequestConfig[]) => DvelopHttpRequestConfig -): DvelopHttpClient { - - return { - request: async (context: DvelopContext, config: DvelopHttpRequestConfig) => { - - const defaultConfig: DvelopHttpRequestConfig = {}; - - defaultConfig.headers = { - "ContentType": "application/json", - "Accept": "application/hal+json, application/json", - }; - - if (context.systemBaseUri) { - defaultConfig.baseURL = context.systemBaseUri; - } - - if (context.authSessionId) { - defaultConfig.headers["Authorization"] = `Bearer ${context.authSessionId}`; - } - - if (context.requestId) { - defaultConfig.headers[DVELOP_REQUEST_ID_HEADER] = context.requestId; - } else { - defaultConfig.headers[DVELOP_REQUEST_ID_HEADER] = generateRequestId(); - } - - if (context.traceContext) { - defaultConfig.headers[TRACEPARENT_HEADER] = buildTraceparentHeader(context.traceContext); - } - - return axiosInstance.request(mergeConfigs(defaultConfig, config)); - } - }; -} - -/* istanbul ignore next */ -export function defaultDvelopHttpClientFactory(): DvelopHttpClient { - return axiosHttpClientFactory(axiosInstanceFactory(axios), generateRequestId, buildTraceparentHeader, deepMergeObjects); -} \ No newline at end of file diff --git a/packages/core/src/http/http-headers.ts b/packages/core/src/http/http-headers.ts deleted file mode 100644 index ba65c326..00000000 --- a/packages/core/src/http/http-headers.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * HTTP Header for the systemBaseUri for the tenant. - */ -export const DVELOP_SYSTEM_BASE_URI_HEADER = "x-dv-baseuri"; - -/** - * HTTP Header for the tenantId for the tenant. - */ -export const DVELOP_TENANT_ID_HEADER = "x-dv-tenant-id"; - -/** - * HTTP Header for the requestId for the request. Forward this id to make calls trackable throughout d.velop apps. - */ -export const DVELOP_REQUEST_ID_HEADER = "x-dv-request-id"; - -/** - * HTTP Header for the requestSignature. **The requestSignature should be validated for every request when recieving calls from the d.velop cloud. - * Refer to the [d.velop basics tenant header section](https://developer.d-velop.de/dev/en/basics#tenant-header) for more information.** - */ -export const DVELOP_REQUEST_SIGNATURE_HEADER = "x-dv-sig-1"; - -/** - * HTTP Header used for [W3C Trace Context specification](https://www.w3.org/TR/trace-context/#traceparent-header). - */ -export const TRACEPARENT_HEADER = "traceparent"; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 88785b90..bbb7d61b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -24,12 +24,12 @@ * @module core */ export { DvelopContext } from "./context/context"; +export { DvelopOptions } from "./options/options"; export * from "./errors/errors"; -export * from "./http/http-headers"; export * as internals from "./internal"; export { DeepMergeError, deepMergeObjects } from "./util/deep-merge-objects"; -export { DvelopHttpRequestConfig, DvelopHttpResponse, DvelopHttpError, DvelopHttpClient, defaultDvelopHttpClientFactory } from "./http/http-client"; export { generateUuid, generateRequestId } from "./generate-uuid/generate-uudi-id"; +export { dvelopFetch } from "./http/fetch"; export { TraceContext } from "./trace-context/trace-context"; export { TraceContextError } from "./trace-context/trace-context-error"; diff --git a/packages/core/src/options/options.ts b/packages/core/src/options/options.ts new file mode 100644 index 00000000..3bd45190 --- /dev/null +++ b/packages/core/src/options/options.ts @@ -0,0 +1,4 @@ +export interface DvelopOptions { + initOverwrite?: Partial; + onResponse?: (response: Response) => T | Promise; +} \ No newline at end of file diff --git a/packages/core/src/trace-context/trace-context-error.ts b/packages/core/src/trace-context/trace-context-error.ts index 439b2d33..7a9c3ee8 100644 --- a/packages/core/src/trace-context/trace-context-error.ts +++ b/packages/core/src/trace-context/trace-context-error.ts @@ -5,7 +5,7 @@ import { DvelopSdkError } from "../errors/errors"; * @category Error */ export class TraceContextError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars + constructor(message: string) { super(message); Object.setPrototypeOf(this, TraceContextError.prototype); diff --git a/packages/core/src/util/deep-merge-objects.ts b/packages/core/src/util/deep-merge-objects.ts index 7064835a..15dca825 100644 --- a/packages/core/src/util/deep-merge-objects.ts +++ b/packages/core/src/util/deep-merge-objects.ts @@ -6,7 +6,7 @@ import merge = require("lodash.merge"); * @category Error */ export class DeepMergeError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars + constructor(message: string) { super(message); Object.setPrototypeOf(this, DeepMergeError.prototype); diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json new file mode 100644 index 00000000..0c9b67d5 --- /dev/null +++ b/packages/core/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig-base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.spec.ts", "src/**/tmp-*.ts"] +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 5ad7fa35..3912f472 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "outDir": "lib", /* Redirect output structure to the directory. */ - "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "noEmit": true, + "composite": false, + "declaration": false, + "declarationMap": false }, - "include": ["src/**/*"], - "exclude": ["src/**/*.spec.ts"], -} \ No newline at end of file + "include": ["src/**/*"] +} diff --git a/packages/dms/package.json b/packages/dms/package.json index a8a84e20..c280e28c 100644 --- a/packages/dms/package.json +++ b/packages/dms/package.json @@ -1,7 +1,7 @@ { "name": "@dvelop-sdk/dms", "description": "This package contains functionality for the DMS-App in the d.velop cloud.", - "version": "1.5.11", + "version": "2.0.0", "license": "Apache-2.0", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -23,6 +23,6 @@ "license": "license-checker --production --onlyAllow Apache-2.0;MIT;ISC;BSD-2-Clause;BSD-3-Clause" }, "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } -} +} \ No newline at end of file diff --git a/packages/dms/src/dms-objects/create-dms-object-note/create-dms-object-note.spec.ts b/packages/dms/src/dms-objects/create-dms-object-note/create-dms-object-note.spec.ts index 0f2fed24..b4ed4e75 100644 --- a/packages/dms/src/dms-objects/create-dms-object-note/create-dms-object-note.spec.ts +++ b/packages/dms/src/dms-objects/create-dms-object-note/create-dms-object-note.spec.ts @@ -1,25 +1,25 @@ -import { DvelopContext } from "@dvelop-sdk/core"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; import { - _createDmsObjectNoteDefaultTransformFunction, - _createDmsObjectNoteFactory, - CreateDmsObjectNoteParams + CreateDmsObjectNoteParams, + onResponse, + createDmsObjectNote, } from "./create-dms-object-note"; -import { HttpResponse } from "../../utils/http"; -describe("createDmsObjectNotes", () => { - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; + +describe("createDmsObjectNote", () => { let context: DvelopContext; let params: CreateDmsObjectNoteParams; beforeEach(() => { jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { repositoryId: "HiItsMeRepositoryId", dmsObjectId: "HiItsMeDmsObjectId", @@ -27,48 +27,29 @@ describe("createDmsObjectNotes", () => { }; }); - it("should make correct request", async () => { - const createDmsObjectNotes = _createDmsObjectNoteFactory(mockHttpRequestFunction, mockTransformFunction); - await createDmsObjectNotes(context, params); + it("should call dvelopFetch with POST and JSON body", async () => { + await createDmsObjectNote(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "POST", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping", "notes"], - templates: { - "repositoryid": params.repositoryId, - "dmsobjectid": params.dmsObjectId - }, - data: { - "text": params.noteText - } - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe(`/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}/n`); + expect(calledInit).toMatchObject({ method: "POST" }); + expect(JSON.parse(calledInit!.body as string)).toEqual({ text: params.noteText }); + expect(calledOptions).toMatchObject({ onResponse: onResponse }); }); - it("should pass response to transform and return transform-result", async () => { - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const createDmsObjectNotes = _createDmsObjectNoteFactory(mockHttpRequestFunction, mockTransformFunction); - await createDmsObjectNotes(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await createDmsObjectNote(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); + describe("_createDmsObjectNoteDefaultTransformFunction", () => { - describe("createDmsObjectNotesDefaultTransformFunction", () => { - it("should return void", async () => { - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - - const createDmsObjectNotes = _createDmsObjectNoteFactory(mockHttpRequestFunction, _createDmsObjectNoteDefaultTransformFunction); - const result = await createDmsObjectNotes(context, params); - - expect(result).toBe(undefined); + it("should resolve to undefined on 2xx", async () => { + const response = new Response(null, { status: 204 }); + await expect(onResponse(response)).resolves.toBeUndefined(); }); }); }); diff --git a/packages/dms/src/dms-objects/create-dms-object-note/create-dms-object-note.ts b/packages/dms/src/dms-objects/create-dms-object-note/create-dms-object-note.ts index 0b8ba7ea..e1fb0537 100644 --- a/packages/dms/src/dms-objects/create-dms-object-note/create-dms-object-note.ts +++ b/packages/dms/src/dms-objects/create-dms-object-note/create-dms-object-note.ts @@ -1,5 +1,5 @@ -import { _defaultHttpRequestFunction, HttpConfig, HttpResponse } from "../../utils/http"; -import { DvelopContext } from "../../index"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; /** * Parameters for the {@link createDmsObjectNote}-function. @@ -21,33 +21,8 @@ export interface CreateDmsObjectNoteParams { * @internal * @category DmsObject */ -export function _createDmsObjectNoteDefaultTransformFunction(_: HttpResponse, __: DvelopContext, ___: CreateDmsObjectNoteParams): void { } // no error indicates sucess - -/** - * Factory for the {@link createDmsObjectNote}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link createDmsObjectNote}-function. A corresponding transformFunction has to be supplied. - * @category DmsObject - */ -export function _createDmsObjectNoteFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: CreateDmsObjectNoteParams) => T -): (context: DvelopContext, params: CreateDmsObjectNoteParams) => Promise { - return async (context: DvelopContext, params: CreateDmsObjectNoteParams) => { - const response: HttpResponse = await httpRequestFunction(context, { - method: "POST", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping", "notes"], - templates: { - "repositoryid": params.repositoryId, - "dmsobjectid": params.dmsObjectId - }, - data: { - "text": params.noteText - } - }); - - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } /** @@ -68,7 +43,17 @@ export function _createDmsObjectNoteFactory( * * @category DmsObject */ -/* istanbul ignore next */ -export async function createDmsObjectNote(context: DvelopContext, params: CreateDmsObjectNoteParams): Promise { - return await _createDmsObjectNoteFactory(_defaultHttpRequestFunction, _createDmsObjectNoteDefaultTransformFunction)(context, params); +export async function createDmsObjectNote(context: DvelopContext, params: CreateDmsObjectNoteParams): Promise; +export async function createDmsObjectNote(context: DvelopContext, params: CreateDmsObjectNoteParams, options: DvelopOptions): Promise; +export async function createDmsObjectNote( + context: DvelopContext, + params: CreateDmsObjectNoteParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}/n`, { + method: "POST", + body: JSON.stringify({ text: params.noteText }) + }, options); } diff --git a/packages/dms/src/dms-objects/create-dms-object/create-dms-object.spec.ts b/packages/dms/src/dms-objects/create-dms-object/create-dms-object.spec.ts index dac7ff12..5faa4a48 100644 --- a/packages/dms/src/dms-objects/create-dms-object/create-dms-object.spec.ts +++ b/packages/dms/src/dms-objects/create-dms-object/create-dms-object.spec.ts @@ -1,239 +1,135 @@ -import { DvelopContext, DmsError } from "../../index"; -import { HttpResponse } from "../../utils/http"; -import { _createDmsObjectDefaultStoreFileFunction, _createDmsObjectDefaultTransformFunction, createDmsObjectFactory, CreateDmsObjectParams } from "./create-dms-object"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { DmsError } from "../../utils/dms-error"; +import { + CreateDmsObjectParams, + onResponse, + createDmsObject, +} from "./create-dms-object"; import { storeFileTemporarily } from "../store-file-temporarily/store-file-temporarily"; +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + jest.mock("../store-file-temporarily/store-file-temporarily"); -const mockStoryFileTemporarily = storeFileTemporarily as jest.MockedFunction; -describe("createDmsObject", () => { +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; +const mockStoreFileTemporarily = storeFileTemporarily as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); - let mockStoreFileFunction = jest.fn(); +describe("createDmsObject", () => { let context: DvelopContext; let params: CreateDmsObjectParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { repositoryId: "HiItsMeRepositoryId", sourceId: "HiItsMeSourceId", categoryId: "HiItsMeCategoryId", properties: [ - { - key: "HiItsMeProperty1Key", - values: ["HiItsMeProperty1Value"] - }, - { - key: "HiItsMeProperty2Key", - values: ["HiItsMeProperty2Value1", "HiItsMeProperty2Value2"] - } + { key: "HiItsMeProperty1Key", values: ["HiItsMeProperty1Value"] }, + { key: "HiItsMeProperty2Key", values: ["HiItsMeProperty2Value1", "HiItsMeProperty2Value2"] } ] }; }); - [ - { param: "contentUri", expectStoreFileCall: false }, - { param: "contentLocationUri", expectStoreFileCall: false } - ].forEach(testCase => { - - it(`should ${testCase.expectStoreFileCall ? "" : "not"} throw on content`, async () => { - - params.contentUri = "HiItsMeContentUri"; - params.content = new ArrayBuffer(42); - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction); - expect(async () => await createDmsObject(context, params)).not.toThrow(); - }); - - }); - - describe("on contentUri", () => { - - it("should pass without storeFileFunction", async () => { - - params.contentUri = "HiItsMeContentUri"; - params.content = new ArrayBuffer(42); - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction); - expect(async () => await createDmsObject(context, params)).not.toThrow(); - }); - - it("should not call storeFileFunction", async () => { - - params.contentUri = "HiItsMeContentUri"; - params.content = new ArrayBuffer(42); - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, mockStoreFileFunction); - await createDmsObject(context, params); + it("should call dvelopFetch with method POST and a JSON body", async () => { + await createDmsObject(context, params); - expect(mockStoreFileFunction).toHaveBeenCalledTimes(0); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe(`/dms/r/${params.repositoryId}/o2m`); + expect(calledInit).toMatchObject({ method: "POST" }); + expect(JSON.parse(calledInit!.body as string)).toEqual({ + sourceId: params.sourceId, + sourceCategory: params.categoryId, + sourceProperties: { properties: params.properties }, + fileName: params.fileName, + contentLocationUri: params.contentLocationUri, + contentUri: params.contentUri }); }); - describe("on contentLocationUri", () => { - - it("should pass without storeFileFunction", async () => { + it("should not call storeFileTemporarily when contentUri is set even if content is given", async () => { + params.contentUri = "HiItsMeContentUri"; + params.content = new ArrayBuffer(42); - params.contentLocationUri = "HiItsMeContenLocationtUri"; - params.content = new ArrayBuffer(42); - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction); - expect(async () => await createDmsObject(context, params)).not.toThrow(); - }); - - it("should not call storeFileFunction if contentLocationUri is given", async () => { - - params.contentLocationUri = "HiItsMeContentLocationUri"; - params.content = new ArrayBuffer(42); - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, mockStoreFileFunction); - await createDmsObject(context, params); + await createDmsObject(context, params); - expect(mockStoreFileFunction).toHaveBeenCalledTimes(0); - }); + expect(mockStoreFileTemporarily).not.toHaveBeenCalled(); }); - describe("on content", () => { - it("should call storeFileFunction if content is given", async () => { + it("should not call storeFileTemporarily when contentLocationUri is set even if content is given", async () => { + params.contentLocationUri = "HiItsMeContentLocationUri"; + params.content = new ArrayBuffer(42); - params.content = new ArrayBuffer(42); - mockStoreFileFunction.mockReturnValue({ setAs: "contentUri", uri: "HiItsMeUri" }); - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, mockStoreFileFunction); - await createDmsObject(context, params); - - expect(mockStoreFileFunction).toHaveBeenCalledTimes(1); - expect(mockStoreFileFunction).toHaveBeenCalledWith(context, params); - }); - - it("should throw on no storeFileFunction", async () => { - - params.content = new ArrayBuffer(42); - mockStoreFileFunction.mockReturnValue({ setAs: "contentUri", uri: "HiItsMeUri" }); - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction); - let expectedError: DmsError; - try { - await createDmsObject(context, params); - } catch (e: any) { - expectedError = e; - } + await createDmsObject(context, params); - expect(expectedError instanceof DmsError).toBeTruthy(); - expect(expectedError.message).toEqual("DmsObject cannot be created with content. No storeFile-function has been supplied."); - }); + expect(mockStoreFileTemporarily).not.toHaveBeenCalled(); }); + it("should call storeFileTemporarily and set contentLocationUri when content is given without contentUri/contentLocationUri", async () => { + params.content = new ArrayBuffer(42); + mockStoreFileTemporarily.mockResolvedValue("HiItsMeTemporaryUri"); - - it("should make correct request", async () => { - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, mockStoreFileFunction); await createDmsObject(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "POST", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping"], - templates: { "repositoryid": params.repositoryId }, - data: { - "sourceId": params.sourceId, - "sourceCategory": params.categoryId, - "sourceProperties": { - "properties": params.properties - }, - "fileName": params.fileName, - "contentLocationUri": params.contentLocationUri, - "contentUri": params.contentUri - } + expect(mockStoreFileTemporarily).toHaveBeenCalledWith(context, { + repositoryId: params.repositoryId, + content: params.content }); + const calledInit = mockDvelopFetch.mock.calls[0][2]; + expect(JSON.parse(calledInit!.body as string).contentLocationUri).toEqual("HiItsMeTemporaryUri"); }); - it("should pass response to transform and return transform-result", async () => { + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await createDmsObject(context, params, options); - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, mockStoreFileFunction); - await createDmsObject(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + const calledOptions = mockDvelopFetch.mock.calls[0][3]; + expect(calledOptions).toBe(options); }); - describe("createDmsObjectDefaultTransformer", () => { + describe("onResponse", () => { - const dmsObjectId: string = "HiItsMeDmsObjectId"; + const dmsObjectId = "HiItsMeDmsObjectId"; [ `/${dmsObjectId}`, `/${dmsObjectId}?`, `hi/i/am/an/uri/${dmsObjectId}`, `hi/i/am/an/uri/${dmsObjectId}?withQueryParams`, - ].forEach(testCase => { - it(`should return parse ${testCase}`, async () => { - - mockHttpRequestFunction.mockResolvedValue({ headers: { location: testCase } }); - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, _createDmsObjectDefaultTransformFunction, mockStoreFileFunction); - const result = await createDmsObject(context, params); - - expect(result).toHaveProperty("repositoryId", params.repositoryId); - expect(result).toHaveProperty("sourceId", params.sourceId); - expect(result).toHaveProperty("dmsObjectId", dmsObjectId); + ].forEach(location => { + it(`should parse location header '${location}'`, async () => { + const response = new Response(null, { status: 201, headers: { location } }); + + const transform = onResponse(params); + const result = await transform(response); + + expect(result).toEqual({ + repositoryId: params.repositoryId, + sourceId: params.sourceId, + dmsObjectId, + }); }); }); - it("should throw on parse error", async () => { + it("should throw DmsError when location cannot be parsed", async () => { + const response = new Response(null, { status: 201, headers: { location: "HiImWrong" } }); + const transform = onResponse(params); - const location: string = "HiImWrong"; - mockHttpRequestFunction.mockResolvedValue({ headers: { location: location } }); - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, _createDmsObjectDefaultTransformFunction, mockStoreFileFunction); - let expectedError: Error; try { - await createDmsObject(context, params); - } catch (e) { - expectedError = e; + await transform(response); + fail("expected throw"); + } catch (e: any) { + expect(e).toBeInstanceOf(DmsError); + expect(e.message).toContain("Failed to parse dmsObjectId"); + expect(e.message).toContain("HiImWrong"); } - - expect(expectedError instanceof DmsError).toBeTruthy(); - expect(expectedError.message).toContain("Failed to parse dmsObjectId"); - expect(expectedError.message).toContain(location); - }); - }); - - describe("createDmsObjectDefaultStoreFileFunction", () => { - - it("should call storeFileTemporarily correctly", async () => { - - const temporaryFileUrl = "HiItsMeTemporaryFileUrl"; - mockStoryFileTemporarily.mockResolvedValue(temporaryFileUrl); - params.content = new ArrayBuffer(42); - - const createDmsObject = createDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, _createDmsObjectDefaultStoreFileFunction); - await createDmsObject(context, params); - - expect(mockStoryFileTemporarily).toHaveBeenCalledTimes(1); - expect(mockStoryFileTemporarily).toHaveBeenCalledWith(context, params); - - expect(mockHttpRequestFunction).toHaveBeenCalledWith(expect.any(Object), expect.objectContaining({ - data: expect.objectContaining({ - contentLocationUri: temporaryFileUrl - }) - })); }); }); }); diff --git a/packages/dms/src/dms-objects/create-dms-object/create-dms-object.ts b/packages/dms/src/dms-objects/create-dms-object/create-dms-object.ts index 72fb0c26..976d14af 100644 --- a/packages/dms/src/dms-objects/create-dms-object/create-dms-object.ts +++ b/packages/dms/src/dms-objects/create-dms-object/create-dms-object.ts @@ -1,7 +1,7 @@ -import { DvelopContext, DmsError } from "../../index"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { DmsError, ensureSuccessResponse } from "../../utils/dms-error"; import { GetDmsObjectParams } from "../get-dms-object/get-dms-object"; -import { storeFileTemporarily, StoreFileTemporarilyParams } from "../store-file-temporarily/store-file-temporarily"; +import { storeFileTemporarily } from "../store-file-temporarily/store-file-temporarily"; /** * Parameters for the {@link createDmsObject}-function. @@ -39,79 +39,27 @@ export interface CreateDmsObjectParams { } /** - * Default transform-function provided to the {@link createDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * Default `onResponse` provided to the {@link createDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category DmsObject */ -export function _createDmsObjectDefaultTransformFunction(response: HttpResponse, _: DvelopContext, params: CreateDmsObjectParams): GetDmsObjectParams { - - const location: string = response.headers["location"] || ""; - const matches: RegExpExecArray | null = /^.*\/(.*?)(\?|$)/.exec(location); - - if (matches) { - return { - repositoryId: params.repositoryId, - sourceId: params.sourceId, - dmsObjectId: matches[1] - }; - } else { - throw new DmsError(`Failed to parse dmsObjectId from '${location}'`); - } -} - -/** - * Default store-file-function provided to the {@link createDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @internal - * @category DmsObject - */ -export async function _createDmsObjectDefaultStoreFileFunction(context: DvelopContext, params: CreateDmsObjectParams): Promise<{ setAs: "contentUri" | "contentLocationUri", uri: string }> { - const uri: string = await storeFileTemporarily(context, params as StoreFileTemporarilyParams); - return { - setAs: "contentLocationUri", - uri: uri - }; -} - -/** - * Factory for the {@link createDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link createDmsObject}-function. A corresponding transformFunction has to be supplied. - * @category DmsObject - */ -export function createDmsObjectFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: CreateDmsObjectParams) => T, - storeFileFunction?: (context: DvelopContext, params: CreateDmsObjectParams) => Promise<{ setAs: "contentUri" | "contentLocationUri", uri: string }> -): (context: DvelopContext, params: CreateDmsObjectParams) => Promise { - - return async (context: DvelopContext, params: CreateDmsObjectParams) => { - - if (!params.contentUri && !params.contentLocationUri && params.content) { - if (storeFileFunction) { - const storedFileInfo: { setAs: "contentUri" | "contentLocationUri", uri: string } = await storeFileFunction(context, params); - params[storedFileInfo.setAs] = storedFileInfo.uri; - } else { - throw new DmsError("DmsObject cannot be created with content. No storeFile-function has been supplied."); - } +export function onResponse( + params: CreateDmsObjectParams +): (response: Response) => Promise { + return async (response: Response) => { + await ensureSuccessResponse(response); + + const location = response.headers.get("location") ?? ""; + const matches = /^.*\/(.*?)(\?|$)/.exec(location); + + if (matches) { + return { + repositoryId: params.repositoryId, + sourceId: params.sourceId, + dmsObjectId: matches[1] + }; } - - const response: HttpResponse = await httpRequestFunction(context, { - method: "POST", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping"], - templates: { "repositoryid": params.repositoryId }, - data: { - "sourceId": params.sourceId, - "sourceCategory": params.categoryId, - "sourceProperties": { - "properties": params.properties - }, - "fileName": params.fileName, - "contentLocationUri": params.contentLocationUri, - "contentUri": params.contentUri - } - }); - - return transformFunction(response, context, params); + throw new DmsError(`Failed to parse dmsObjectId from '${location}'`); }; } @@ -144,7 +92,32 @@ export function createDmsObjectFactory( * ``` * @category DmsObject */ -/* istanbul ignore next */ -export async function createDmsObject(context: DvelopContext, params: CreateDmsObjectParams): Promise { - return await createDmsObjectFactory(_defaultHttpRequestFunction, _createDmsObjectDefaultTransformFunction, _createDmsObjectDefaultStoreFileFunction)(context, params); -} \ No newline at end of file +export async function createDmsObject(context: DvelopContext, params: CreateDmsObjectParams): Promise; +export async function createDmsObject(context: DvelopContext, params: CreateDmsObjectParams, options: DvelopOptions): Promise; +export async function createDmsObject( + context: DvelopContext, + params: CreateDmsObjectParams, + options: DvelopOptions = { + onResponse: onResponse(params) + } +): Promise { + + if (!params.contentUri && !params.contentLocationUri && params.content) { + params.contentLocationUri = await storeFileTemporarily(context, { + repositoryId: params.repositoryId, + content: params.content + }); + } + + return dvelopFetch(context, `/dms/r/${params.repositoryId}/o2m`, { + method: "POST", + body: JSON.stringify({ + sourceId: params.sourceId, + sourceCategory: params.categoryId, + sourceProperties: { "properties": params.properties }, + fileName: params.fileName, + contentLocationUri: params.contentLocationUri, + contentUri: params.contentUri + }) + }, options); +} diff --git a/packages/dms/src/dms-objects/delete-current-dms-object-version/delete-current-dms-object-version.spec.ts b/packages/dms/src/dms-objects/delete-current-dms-object-version/delete-current-dms-object-version.spec.ts index f16e1460..12eb9a12 100644 --- a/packages/dms/src/dms-objects/delete-current-dms-object-version/delete-current-dms-object-version.spec.ts +++ b/packages/dms/src/dms-objects/delete-current-dms-object-version/delete-current-dms-object-version.spec.ts @@ -1,30 +1,25 @@ -import { DvelopContext, ForbiddenError } from "../../index"; -import { HttpResponse } from "../../utils/http"; -import { _getDmsObjectFactory } from "../get-dms-object/get-dms-object"; -import { DeleteCurrentDmsObjectVersionParams, _deleteCurrentDmsObjectVersionFactory, _deleteCurrentDmsObjectVersionDefaultTransformFunction } from "./delete-current-dms-object-version"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { + DeleteCurrentDmsObjectVersionParams, + onResponse, + deleteCurrentDmsObjectVersion, +} from "./delete-current-dms-object-version"; -jest.mock("../get-dms-object/get-dms-object"); -const mockGetDmsObjectFactory = _getDmsObjectFactory as jest.MockedFunction; +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); -describe("deleteCurrentDmsObjectVersion", () => { +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockGetDmsObject = jest.fn(); - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("deleteCurrentDmsObjectVersion", () => { let context: DvelopContext; let params: DeleteCurrentDmsObjectVersionParams; beforeEach(() => { - jest.resetAllMocks(); - mockGetDmsObjectFactory.mockReturnValue(mockGetDmsObject); - - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { repositoryId: "HiItsMeRepositoryId", sourceId: "HiItsMeSourceId", @@ -33,108 +28,43 @@ describe("deleteCurrentDmsObjectVersion", () => { }; }); - it("should handle getDmsObject correctly", async () => { - mockGetDmsObject.mockResolvedValue({ data: { _links: { delete: { href: "HiItsMeHref" } } } }); - - const deleteCurrentDmsObjectVersion = _deleteCurrentDmsObjectVersionFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method DELETE and reason body", async () => { await deleteCurrentDmsObjectVersion(context, params); - expect(mockGetDmsObjectFactory).toHaveBeenCalledTimes(1); - expect(mockGetDmsObjectFactory).toHaveBeenCalledWith(mockHttpRequestFunction, expect.any(Function)); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe(`/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}`); + expect(calledInit).toMatchObject({ method: "DELETE" }); + expect(JSON.parse(calledInit!.body as string)).toEqual({ reason: params.reason }); + expect(calledOptions).toMatchObject({ onResponse: onResponse }); }); - [ - { should: "use delete-href", href: "HiItsMeHref", _links: { delete: { href: "HiItsMeHref" } } }, - { should: "use deleteWithReason-href", href: "HiItsMeHref", _links: { deleteWithReason: { href: "HiItsMeHref" } } }, - { should: "prioritize deleteWithReason-href", href: "HiItsMeHref", _links: { deleteWithReason: { href: "HiItsMeHref" }, delete: { href: "HiImWrong" } } }, - { should: "prioritize deleteWithReason-href", href: "HiItsMeHref", _links: { delete: { href: "HiImWrong" }, deleteWithReason: { href: "HiItsMeHref" } } } - ].forEach(testCase => { - it(`should ${testCase.should}`, async () => { - - mockGetDmsObject.mockResolvedValue({ - data: { - _links: testCase._links - } - }); - - const deleteCurrentDmsObjectVersion = _deleteCurrentDmsObjectVersionFactory(mockHttpRequestFunction, mockTransformFunction); - await deleteCurrentDmsObjectVersion(context, params); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "DELETE", - url: testCase.href, - data: { - reason: params.reason - } - }); - }); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await deleteCurrentDmsObjectVersion(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - it("should throw on no deletion-href", async () => { - - mockGetDmsObject.mockResolvedValue({ - data: { - _links: {} - } - }); + describe("_deleteCurrentDmsObjectVersionDefaultTransformFunction", () => { - const deleteCurrentDmsObjectVersion = _deleteCurrentDmsObjectVersionFactory(mockHttpRequestFunction, mockTransformFunction); - - let expectedError: any; - try { await deleteCurrentDmsObjectVersion(context, params); } - catch (e: any) { - expectedError = e; + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); } - expect(expectedError instanceof ForbiddenError).toBeTruthy(); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(0); - }); - - - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - - mockGetDmsObject.mockResolvedValue({ data: { _links: { delete: { href: "HiItsMeDeleteHref" } } } }); - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const deleteCurrentDmsObjectVersion = _deleteCurrentDmsObjectVersionFactory(mockHttpRequestFunction, mockTransformFunction); - const result: boolean = await deleteCurrentDmsObjectVersion(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); - expect(result).toEqual(transformResult); - }); - - describe("getDmsObjectFileDefaultTransformFunction", () => { - [ - { should: "return true on no relevant links", data: undefined, expected: true }, - { should: "return true on no relevant links", data: { _links: undefined }, expected: true }, - { should: "return true on no relevant links", data: { _links: {} }, expected: true }, - { should: "return true on no relevant links", data: { _links: { irrelevant: { href: "HiItsMeHref" } } }, expected: true }, - { should: "return false on delete-href", data: { _links: { deleteWithReason: { href: "HiItsMeHref" } } }, expected: false }, - { should: "return false on delete-href", data: { _links: { delete: { href: "HiItsMeHref" } } }, expected: false }, - { should: "return false on delete- and deleteWithReason-href", data: { _links: { deleteWithReason: { href: "HiItsMeHref" }, delete: { href: "HiImWrong" } } }, expected: false }, - { should: "return false on delete- and deleteWithReason-href", data: { _links: { delete: { href: "HiImWrong" }, deleteWithReason: { href: "HiItsMeHref" } } }, expected: false } + { name: "no body", make: () => new Response(null, { status: 204 }), expected: true }, + { name: "no _links", make: () => jsonResponse({}), expected: true }, + { name: "empty _links", make: () => jsonResponse({ _links: {} }), expected: true }, + { name: "unrelated _links", make: () => jsonResponse({ _links: { irrelevant: { href: "x" } } }), expected: true }, + { name: "delete link", make: () => jsonResponse({ _links: { delete: { href: "x" } } }), expected: false }, + { name: "deleteWithReason link", make: () => jsonResponse({ _links: { deleteWithReason: { href: "x" } } }), expected: false }, + { name: "both delete links", make: () => jsonResponse({ _links: { delete: { href: "x" }, deleteWithReason: { href: "y" } } }), expected: false }, ].forEach(testCase => { - it(`should ${testCase.should}`, async () => { - - const response: HttpResponse = { - data: testCase.data - } as HttpResponse; - - mockGetDmsObject.mockResolvedValue({ data: { _links: { delete: { href: "HiItsMeDeleteHref" } } } }); - mockHttpRequestFunction.mockResolvedValue(response); - - const deleteCurrentDmsObjectVersion = _deleteCurrentDmsObjectVersionFactory(mockHttpRequestFunction, _deleteCurrentDmsObjectVersionDefaultTransformFunction); - const result = await deleteCurrentDmsObjectVersion(context, params); - + it(`should return ${testCase.expected} on ${testCase.name}`, async () => { + const result = await onResponse(testCase.make()); expect(result).toBe(testCase.expected); }); }); }); -}); \ No newline at end of file +}); diff --git a/packages/dms/src/dms-objects/delete-current-dms-object-version/delete-current-dms-object-version.ts b/packages/dms/src/dms-objects/delete-current-dms-object-version/delete-current-dms-object-version.ts index 7750353f..5d62c323 100644 --- a/packages/dms/src/dms-objects/delete-current-dms-object-version/delete-current-dms-object-version.ts +++ b/packages/dms/src/dms-objects/delete-current-dms-object-version/delete-current-dms-object-version.ts @@ -1,6 +1,5 @@ -import { DvelopContext, ForbiddenError } from "../../index"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; -import { _getDmsObjectFactory } from "../get-dms-object/get-dms-object"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; /** * Parameters for the {@link deleteCurrentDmsObjectVersion}-function. @@ -19,50 +18,23 @@ export interface DeleteCurrentDmsObjectVersionParams { /** * Default transform-function provided to the {@link deleteCurrentDmsObjectVersion}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * + * Resolves to `true` when no further version exists (the DmsObject is fully deleted) + * and to `false` when the response indicates that another version can still be deleted. * @internal * @category DmsObject */ -export function _deleteCurrentDmsObjectVersionDefaultTransformFunction(response: HttpResponse, _: DvelopContext, __: DeleteCurrentDmsObjectVersionParams): boolean { - if (response.data?._links?.deleteWithReason || response.data?._links?.delete) { - return false; - } else { - return true; - } -} - -/** - * Factory for the {@link deleteCurrentDmsObjectVersion}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link deleteCurrentDmsObjectVersion}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category DmsObject - */ -export function _deleteCurrentDmsObjectVersionFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: DeleteCurrentDmsObjectVersionParams) => T, -): (context: DvelopContext, params: DeleteCurrentDmsObjectVersionParams) => Promise { - return async (context: DvelopContext, params: DeleteCurrentDmsObjectVersionParams) => { - - const getDmsObjectResponse: HttpResponse = await _getDmsObjectFactory(httpRequestFunction, (response: HttpResponse) => response)(context, params); +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); - let url: string; - if (getDmsObjectResponse.data._links.deleteWithReason) { - url = getDmsObjectResponse.data._links.deleteWithReason.href; - } else if (getDmsObjectResponse.data._links.delete) { - url = getDmsObjectResponse.data._links.delete.href; - } else { - throw new ForbiddenError("Deletion denied for user."); - } - - const response: HttpResponse = await httpRequestFunction(context, { - method: "DELETE", - url: url, - data: { - reason: params.reason - } - }); + let data: any; + try { + data = await response.clone().json(); + } catch { + data = undefined; + } - return transformFunction(response, context, params); - }; + return !(data?._links?.deleteWithReason || data?._links?.delete); } /** @@ -81,26 +53,21 @@ export function _deleteCurrentDmsObjectVersionFactory( * dmsObjectId: "GDYQ3PJKrT8", * reason: "This shall be gone! Tout de suite!" * }); - * - * // Delete the whole DmsObject - * // * Attention: This method wraps a HTTP-Call in a loop and can significantly slow down your code * - * let deletedAllVersions: boolean = false; - * while (!deletedAllVersions) { - * deletedAllVersions = await deleteCurrentDmsObjectVersion({ - * systemBaseUri: "https://steamwheedle-cartel.d-velop.cloud", - * authSessionId: "dQw4w9WgXcQ" - * }, { - * repositoryId: "qnydFmqHuVo", - * sourceId: "/dms/r/qnydFmqHuVo/source", - * dmsObjectId: "GDYQ3PJKrT8", - * reason: "This shall be gone! Tout de suite!" - * }); - * } * ``` * * @category DmsObject */ -/* istanbul ignore next */ -export async function deleteCurrentDmsObjectVersion(context: DvelopContext, params: DeleteCurrentDmsObjectVersionParams): Promise { - return _deleteCurrentDmsObjectVersionFactory(_defaultHttpRequestFunction, _deleteCurrentDmsObjectVersionDefaultTransformFunction)(context, params); +export async function deleteCurrentDmsObjectVersion(context: DvelopContext, params: DeleteCurrentDmsObjectVersionParams): Promise; +export async function deleteCurrentDmsObjectVersion(context: DvelopContext, params: DeleteCurrentDmsObjectVersionParams, options: DvelopOptions): Promise; +export async function deleteCurrentDmsObjectVersion( + context: DvelopContext, + params: DeleteCurrentDmsObjectVersionParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}`, { + method: "DELETE", + body: JSON.stringify({ reason: params.reason }) + }, options); } diff --git a/packages/dms/src/dms-objects/get-dms-object-file/get-dms-obejct-file.spec.ts b/packages/dms/src/dms-objects/get-dms-object-file/get-dms-obejct-file.spec.ts deleted file mode 100644 index 91c91856..00000000 --- a/packages/dms/src/dms-objects/get-dms-object-file/get-dms-obejct-file.spec.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { DvelopContext, NotFoundError } from "../../index"; -import { HttpResponse } from "../../utils/http"; -import { GetDmsObjectParams } from "../get-dms-object/get-dms-object"; -import { getDmsObjectFileDefaultTransformFunction, _getDmsObjectMainFileFactory, _getDmsObjectPdfFileFactory } from "./get-dms-object-file"; - -[ - { - name: "getDmsObjectMainFile", factory: _getDmsObjectMainFileFactory, follow: "mainblobcontent" - }, - { - name: "getDmsObjectPdfFile", factory: _getDmsObjectPdfFileFactory, follow: "pdfblobcontent" - } -].forEach(testCase => { - describe(`${testCase.name}`, () => { - - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); - - let context: DvelopContext; - let params: GetDmsObjectParams; - - beforeEach(() => { - - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - - params = { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId", - dmsObjectId: "HiItsMeDmsObjectId" - }; - }); - - it("should make correct request", async () => { - - const functionImplementation = testCase.factory(mockHttpRequestFunction, mockTransformFunction); - await functionImplementation(context, params); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/dms", - headers: { - "Accept": "application/octet-stream" - }, - responseType: "arraybuffer", - follows: ["repo", "dmsobjectwithmapping", testCase.follow], - templates: { - "repositoryid": params.repositoryId, - "sourceid": params.sourceId, - "dmsobjectid": params.dmsObjectId - } - }); - }); - - it("should map NoHalJsonLinksError to NotFoundError", async () => { - - const noLinksError: NotFoundError = new NotFoundError("HiItsMeFollow", {} as any); - mockHttpRequestFunction.mockRejectedValue(noLinksError); - - const functionImplementation = testCase.factory(mockHttpRequestFunction, mockTransformFunction); - - let expectedError: Error; - try { - await functionImplementation(context, params); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotFoundError).toBeTruthy(); - }); - - it("should rethrow unknown errors", async () => { - const error: Error = new Error("HiItsMeError"); - mockHttpRequestFunction.mockRejectedValue(error); - - const functionImplementation = testCase.factory(mockHttpRequestFunction, mockTransformFunction); - - let expectedError: Error; - try { - await functionImplementation(context, params); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError).toBe(error); - }); - - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const functionImplementation = testCase.factory(mockHttpRequestFunction, mockTransformFunction); - await functionImplementation(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); - }); - - describe("getDmsObjectFileDefaultTransformFunction", () => { - - it("should return response ArrayBuffer", async () => { - const file: ArrayBuffer = new ArrayBuffer(42); - const response: HttpResponse = { data: file } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - - const functionImplementation = testCase.factory(mockHttpRequestFunction, getDmsObjectFileDefaultTransformFunction); - const result = await functionImplementation(context, params); - - expect(result).toEqual(file); - }); - }); - }); -}); diff --git a/packages/dms/src/dms-objects/get-dms-object-file/get-dms-object-file.spec.ts b/packages/dms/src/dms-objects/get-dms-object-file/get-dms-object-file.spec.ts new file mode 100644 index 00000000..eda62f84 --- /dev/null +++ b/packages/dms/src/dms-objects/get-dms-object-file/get-dms-object-file.spec.ts @@ -0,0 +1,167 @@ +import { DvelopContext, dvelopFetch, NotFoundError } from "@dvelop-sdk/core"; +import { GetDmsObjectParams } from "../get-dms-object/get-dms-object"; +import { onResponse, getDmsObjectMainFile, getDmsObjectPdfFile } from "./get-dms-object-file"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; + +const metadataUrl = (params: GetDmsObjectParams) => + `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}?sourceid=${params.sourceId}`; + +[ + { name: "getDmsObjectMainFile", fn: getDmsObjectMainFile, linkName: "mainblobcontent" }, + { name: "getDmsObjectPdfFile", fn: getDmsObjectPdfFile, linkName: "pdfblobcontent" }, +].forEach(testCase => { + + describe(testCase.name, () => { + + let context: DvelopContext; + let params: GetDmsObjectParams; + + beforeEach(() => { + jest.resetAllMocks(); + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; + params = { + repositoryId: "HiItsMeRepositoryId", + sourceId: "HiItsMeSourceId", + dmsObjectId: "HiItsMeDmsObjectId" + }; + }); + + it("should call dvelopFetch to get metadata, then fetch file with octet-stream Accept header", async () => { + const href = "HiItsMeHref"; + mockDvelopFetch.mockResolvedValueOnce(href); + + await testCase.fn(context, params); + + expect(mockDvelopFetch).toHaveBeenCalledTimes(2); + expect(mockDvelopFetch).toHaveBeenNthCalledWith(1, + context, + metadataUrl(params), + { method: "GET" }, + expect.objectContaining({ onResponse: expect.any(Function) }) + ); + expect(mockDvelopFetch).toHaveBeenNthCalledWith(2, + context, + href, + { method: "GET", headers: { "Accept": "application/octet-stream" } }, + expect.objectContaining({ onResponse: expect.any(Function) }) + ); + }); + + it("should extract link href from metadata JSON response", async () => { + const linkHref = "HiItsMeLinkHref"; + mockDvelopFetch.mockImplementationOnce(async (_ctx, _url, _init, opts: any) => { + const response = new Response(JSON.stringify({ + _links: { [testCase.linkName]: { href: linkHref } } + }), { status: 200, headers: { "Content-Type": "application/json" } }); + return opts.onResponse(response); + }); + + await testCase.fn(context, params); + + expect(mockDvelopFetch).toHaveBeenNthCalledWith(2, + context, + linkHref, + { method: "GET", headers: { "Accept": "application/octet-stream" } }, + expect.objectContaining({ onResponse: expect.any(Function) }) + ); + }); + + it("should throw NotFoundError when link is absent from metadata", async () => { + mockDvelopFetch.mockResolvedValueOnce(undefined); + + try { + await testCase.fn(context, params); + fail("expected throw"); + } catch (e: any) { + expect(e).toBeInstanceOf(NotFoundError); + expect(e.message).toContain(params.dmsObjectId); + expect(e.message).toContain(params.repositoryId); + } + }); + + it("should rewrap NotFoundError from metadata fetch", async () => { + mockDvelopFetch.mockRejectedValueOnce(new NotFoundError("inner", undefined)); + + try { + await testCase.fn(context, params); + fail("expected throw"); + } catch (e: any) { + expect(e).toBeInstanceOf(NotFoundError); + expect(e.message).toContain(params.dmsObjectId); + expect(e.message).toContain(params.repositoryId); + } + }); + + it("should rewrap NotFoundError from file fetch", async () => { + mockDvelopFetch.mockResolvedValueOnce("HiItsMeHref"); + mockDvelopFetch.mockRejectedValueOnce(new NotFoundError("inner", undefined)); + + try { + await testCase.fn(context, params); + fail("expected throw"); + } catch (e: any) { + expect(e).toBeInstanceOf(NotFoundError); + expect(e.message).toContain(params.dmsObjectId); + expect(e.message).toContain(params.repositoryId); + } + }); + + it("should rethrow unknown errors", async () => { + const error = new Error("HiItsMeError"); + mockDvelopFetch.mockRejectedValueOnce(error); + await expect(testCase.fn(context, params)).rejects.toBe(error); + }); + + it("should forward caller-supplied options to file fetch", async () => { + const href = "HiItsMeHref"; + mockDvelopFetch.mockResolvedValueOnce(href); + const options = { onResponse: jest.fn() }; + + await testCase.fn(context, params, options); + + expect(mockDvelopFetch).toHaveBeenNthCalledWith(2, + context, + href, + { method: "GET", headers: { "Accept": "application/octet-stream" } }, + options + ); + }); + + it("should forward initOverwrite to metadata fetch", async () => { + const href = "HiItsMeHref"; + mockDvelopFetch.mockResolvedValueOnce(href); + const initOverwrite = { headers: { "X-Custom": "value" } }; + const options = { onResponse: jest.fn(), initOverwrite }; + + await testCase.fn(context, params, options); + + expect(mockDvelopFetch).toHaveBeenNthCalledWith(1, + context, + metadataUrl(params), + { method: "GET" }, + expect.objectContaining({ initOverwrite }) + ); + }); + }); +}); + +describe("onResponse", () => { + + it("should return ArrayBuffer body on 2xx", async () => { + const buf = new ArrayBuffer(42); + const response = new Response(buf, { status: 200 }); + const result = await onResponse(response); + expect(result.byteLength).toBe(42); + }); + + it("should throw NotFoundError on 404", async () => { + const response = new Response(null, { status: 404 }); + await expect(onResponse(response)).rejects.toBeInstanceOf(NotFoundError); + }); +}); diff --git a/packages/dms/src/dms-objects/get-dms-object-file/get-dms-object-file.ts b/packages/dms/src/dms-objects/get-dms-object-file/get-dms-object-file.ts index 5dbc4598..f964bf0c 100644 --- a/packages/dms/src/dms-objects/get-dms-object-file/get-dms-object-file.ts +++ b/packages/dms/src/dms-objects/get-dms-object-file/get-dms-object-file.ts @@ -1,77 +1,52 @@ -import { DvelopContext, NotFoundError } from "../../index"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; -import { GetDmsObjectParams } from "../../dms-objects/get-dms-object/get-dms-object"; +import { DvelopContext, DvelopOptions, dvelopFetch, NotFoundError } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; +import { GetDmsObjectParams } from "../get-dms-object/get-dms-object"; /** * Default transform-function provided to the {@link getDmsObjectMainFile}- and {@link getDmsObjectPdfFile}-function. * @internal * @category DmsObject */ -export async function getDmsObjectFileDefaultTransformFunction(response: HttpResponse, _: DvelopContext, __: GetDmsObjectParams) { - return response.data; +export async function onResponse(response: Response): Promise { + if (response.status === 404) { + throw new NotFoundError("No File found for dmsObject."); + } + await ensureSuccessResponse(response); + return await response.arrayBuffer(); } -async function getDmsObjectBlobContentRespone( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise>, - follow: string, +export async function fetchDmsObjectFile( context: DvelopContext, - params: GetDmsObjectParams -): Promise> { - try { - return await httpRequestFunction(context, { - method: "GET", - url: "/dms", - headers: { - "Accept": "application/octet-stream" - }, - responseType: "arraybuffer", - follows: ["repo", "dmsobjectwithmapping", follow], - templates: { - "repositoryid": params.repositoryId, - "sourceid": params.sourceId, - "dmsobjectid": params.dmsObjectId - } - }); - } catch (e: any) { - if (e instanceof NotFoundError) { - throw new NotFoundError(`No File found for dmsObject '${params.dmsObjectId} in repository ${params.repositoryId}.`); - } else { - throw e; - } - } + url: string, + options?: DvelopOptions, +): Promise { + return dvelopFetch(context, url, { + method: "GET", + headers: { "Accept": "application/octet-stream" } + }, options ?? { onResponse }); } -/** - * Factory for {@link getDmsObjectMainFile}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the getRepositories-function. A corresponding transformFunction has to be supplied. - * @internal - * @category DmsObject - */ -export function _getDmsObjectMainFileFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: GetDmsObjectParams) => T -): (context: DvelopContext, params: GetDmsObjectParams) => Promise { - return async (context: DvelopContext, params: GetDmsObjectParams) => { - const response: HttpResponse = await getDmsObjectBlobContentRespone(httpRequestFunction, "mainblobcontent", context, params); - return transformFunction(response, context, params); - }; +async function getDmsObjectLinkHref( + context: DvelopContext, + params: GetDmsObjectParams, + linkName: string, + initOverwrite?: RequestInit, +): Promise { + return dvelopFetch( + context, + `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}?sourceid=${params.sourceId}`, + { method: "GET" }, + { + initOverwrite, + onResponse: async (response: Response) => { + await ensureSuccessResponse(response); + const data: any = await response.json(); + return data._links?.[linkName]?.href as string | undefined; + } + } + ); } -/** - * Factory for {@link getDmsObjectPdfFile}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the getRepositories-function. A corresponding transformFunction has to be supplied. - * @internal - * @category DmsObject - */ -export function _getDmsObjectPdfFileFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: GetDmsObjectParams) => T -): (context: DvelopContext, params: GetDmsObjectParams) => Promise { - return async (context: DvelopContext, params: GetDmsObjectParams) => { - const response: HttpResponse = await getDmsObjectBlobContentRespone(httpRequestFunction, "pdfblobcontent", context, params); - return transformFunction(response, context, params); - }; -} /** * Download a DmsObject-file. * @@ -92,9 +67,33 @@ export function _getDmsObjectPdfFileFactory( * ``` * @category DmsObject */ -/* istanbul ignore next */ -export async function getDmsObjectMainFile(context: DvelopContext, params: GetDmsObjectParams): Promise { - return _getDmsObjectMainFileFactory(_defaultHttpRequestFunction, getDmsObjectFileDefaultTransformFunction)(context, params); +export async function getDmsObjectMainFile(context: DvelopContext, params: GetDmsObjectParams): Promise; +export async function getDmsObjectMainFile(context: DvelopContext, params: GetDmsObjectParams, options: DvelopOptions): Promise; +export async function getDmsObjectMainFile( + context: DvelopContext, + params: GetDmsObjectParams, + options?: DvelopOptions, +): Promise { + let href: string | undefined; + try { + href = await getDmsObjectLinkHref(context, params, "mainblobcontent", options?.initOverwrite); + } catch (e: any) { + if (e instanceof NotFoundError) { + throw new NotFoundError(`No main file found for dmsObject '${params.dmsObjectId}' in repository '${params.repositoryId}'.`); + } + throw e; + } + if (!href) { + throw new NotFoundError(`No main file found for dmsObject '${params.dmsObjectId}' in repository '${params.repositoryId}'.`); + } + try { + return await fetchDmsObjectFile(context, href, options); + } catch (e: any) { + if (e instanceof NotFoundError) { + throw new NotFoundError(`No main file found for dmsObject '${params.dmsObjectId}' in repository '${params.repositoryId}'.`); + } + throw e; + } } /** @@ -117,7 +116,31 @@ export async function getDmsObjectMainFile(context: DvelopContext, params: GetDm * ``` * @category DmsObject */ -/* istanbul ignore next */ -export async function getDmsObjectPdfFile(context: DvelopContext, params: GetDmsObjectParams): Promise { - return _getDmsObjectPdfFileFactory(_defaultHttpRequestFunction, getDmsObjectFileDefaultTransformFunction)(context, params); +export async function getDmsObjectPdfFile(context: DvelopContext, params: GetDmsObjectParams): Promise; +export async function getDmsObjectPdfFile(context: DvelopContext, params: GetDmsObjectParams, options: DvelopOptions): Promise; +export async function getDmsObjectPdfFile( + context: DvelopContext, + params: GetDmsObjectParams, + options?: DvelopOptions, +): Promise { + let href: string | undefined; + try { + href = await getDmsObjectLinkHref(context, params, "pdfblobcontent", options?.initOverwrite); + } catch (e: any) { + if (e instanceof NotFoundError) { + throw new NotFoundError(`No PDF file found for dmsObject '${params.dmsObjectId}' in repository '${params.repositoryId}'.`); + } + throw e; + } + if (!href) { + throw new NotFoundError(`No PDF file found for dmsObject '${params.dmsObjectId}' in repository '${params.repositoryId}'.`); + } + try { + return await fetchDmsObjectFile(context, href, options); + } catch (e: any) { + if (e instanceof NotFoundError) { + throw new NotFoundError(`No PDF file found for dmsObject '${params.dmsObjectId}' in repository '${params.repositoryId}'.`); + } + throw e; + } } diff --git a/packages/dms/src/dms-objects/get-dms-object-notes/get-dms-object-notes.spec.ts b/packages/dms/src/dms-objects/get-dms-object-notes/get-dms-object-notes.spec.ts index 14b67ac7..89e3ee58 100644 --- a/packages/dms/src/dms-objects/get-dms-object-notes/get-dms-object-notes.spec.ts +++ b/packages/dms/src/dms-objects/get-dms-object-notes/get-dms-object-notes.spec.ts @@ -1,126 +1,78 @@ -import { DvelopContext } from "@dvelop-sdk/core"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; import { - _getDmsObjectNotesDefaultTransformFunction, - _getDmsObjectNotesFactory, + DmsObjectNote, GetDmsObjectNotesParams, - DmsObjectNote + onResponse, + getDmsObjectNotes, } from "./get-dms-object-notes"; -import { HttpResponse } from "../../utils/http"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; describe("getDmsObjectNotes", () => { - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); let context: DvelopContext; let params: GetDmsObjectNotesParams; beforeEach(() => { jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { repositoryId: "HiItsMeRepositoryId", dmsObjectId: "HiItsMeDmsObjectId" }; }); - it("should make correct request", async () => { - const getDmsObjectNotes = _getDmsObjectNotesFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method GET", async () => { await getDmsObjectNotes(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping", "notes"], - templates: { - "repositoryid": params.repositoryId, - "dmsobjectid": params.dmsObjectId - } - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}/n`, + { method: "GET" }, + expect.objectContaining({ onResponse: onResponse }) + ); + }); + + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await getDmsObjectNotes(context, params, options); + expect(mockDvelopFetch).toHaveBeenCalledWith(context, `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}/n`, { method: "GET" }, options); }); - it("should pass response to transform and return transform-result", async () => { - const response: HttpResponse = { - data: { + describe("_getDmsObjectNotesDefaultTransformFunction", () => { + + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } + + it("should map a single note", async () => { + const data = { notes: [{ - creator: { - id: "HiItsMeCreatorId", - displayName: "HiItsMeCreatorDisplayName" - }, + creator: { id: "HiItsMeCreatorId", displayName: "HiItsMeCreatorDisplayName" }, text: "HiItsMeText", created: "2023-10-11T09:09:09.453+02:00" }] - } - } as HttpResponse; + }; - const transformResult: DmsObjectNote[] = [{ - creator: { - id: "HiItsMeCreatorId", - displayName: "HiItsMeCreatorDisplayName" - }, - text: "HiItsMeText", - created: new Date("2023-10-11T09:09:09.453+02:00") - }]; - - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const getDmsObjectNotes = _getDmsObjectNotesFactory(mockHttpRequestFunction, mockTransformFunction); - await getDmsObjectNotes(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); - }); - - describe("getDmsObjectNotesDefaultTransformFunction", () => { - it("should return response DmsObjectNotes with single note", async () => { - const response: HttpResponse = { - data: { - notes: [{ - creator: { - id: "HiItsMeCreatorId", - displayName: "HiItsMeCreatorDisplayName" - }, - text: "HiItsMeText", - created: "2023-10-11T09:09:09.453+02:00" - }] - } - } as HttpResponse; - - const expectedResult: DmsObjectNote[] = [{ - creator: { - id: "HiItsMeCreatorId", - displayName: "HiItsMeCreatorDisplayName" - }, + const expected: DmsObjectNote[] = [{ + creator: { id: "HiItsMeCreatorId", displayName: "HiItsMeCreatorDisplayName" }, text: "HiItsMeText", created: new Date("2023-10-11T09:09:09.453+02:00") }]; - mockHttpRequestFunction.mockResolvedValue(response); - const getDmsObjectNotes = _getDmsObjectNotesFactory(mockHttpRequestFunction, _getDmsObjectNotesDefaultTransformFunction); - const result = await getDmsObjectNotes(context, params); - - expect(result).toEqual(expectedResult); + const result = await onResponse(jsonResponse(data)); + expect(result).toEqual(expected); }); - it("should return response DmsObjectNotes without notes when dmsObject has no notes", async () => { - const response: HttpResponse = { - data: { - notes: [] - } - } as HttpResponse; - - const expectedResult: any = []; - - mockHttpRequestFunction.mockResolvedValue(response); - const getDmsObjectNotes = _getDmsObjectNotesFactory(mockHttpRequestFunction, _getDmsObjectNotesDefaultTransformFunction); - const result = await getDmsObjectNotes(context, params); - - expect(result).toEqual(expectedResult); + it("should map empty notes list", async () => { + const result = await onResponse(jsonResponse({ notes: [] })); + expect(result).toEqual([]); }); }); }); diff --git a/packages/dms/src/dms-objects/get-dms-object-notes/get-dms-object-notes.ts b/packages/dms/src/dms-objects/get-dms-object-notes/get-dms-object-notes.ts index 126126ce..38bb4f80 100644 --- a/packages/dms/src/dms-objects/get-dms-object-notes/get-dms-object-notes.ts +++ b/packages/dms/src/dms-objects/get-dms-object-notes/get-dms-object-notes.ts @@ -1,5 +1,5 @@ -import { _defaultHttpRequestFunction, HttpConfig, HttpResponse } from "../../utils/http"; -import { DvelopContext } from "../../index"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; /** * Parameters for the {@link getDmsObjectNotes}-function. @@ -35,43 +35,17 @@ export interface DmsObjectNote { * @internal * @category DmsObject */ -export function _getDmsObjectNotesDefaultTransformFunction(response: HttpResponse, _: DvelopContext, __: GetDmsObjectNotesParams): DmsObjectNote[] { - const mappedNotes: DmsObjectNote[] = response.data.notes.map((note: DmsObjectNote) => { - return { - creator: { - id: note.creator.id, - displayName: note.creator.displayName - }, - text: note.text, - created: new Date(note.created) - }; - }); - - return mappedNotes; -} - -/** - * Factory for the {@link getDmsObjectNotes}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link getDmsObjectNotes}-function. A corresponding transformFunction has to be supplied. - * @category DmsObject - */ -export function _getDmsObjectNotesFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: GetDmsObjectNotesParams) => T -): (context: DvelopContext, params: GetDmsObjectNotesParams) => Promise { - return async (context: DvelopContext, params: GetDmsObjectNotesParams) => { - const response: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping", "notes"], - templates: { - "repositoryid": params.repositoryId, - "dmsobjectid": params.dmsObjectId - } - }); - - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + const data: any = await response.json(); + return data.notes.map((note: any) => ({ + creator: { + id: note.creator.id, + displayName: note.creator.displayName + }, + text: note.text, + created: new Date(note.created) + })); } /** @@ -91,14 +65,18 @@ export function _getDmsObjectNotesFactory( * notes.forEach(n => { * console.log(`${n.creator.displayName}: "${n.text}"`); * }); - * - * // Jastor Gallywix: "I need this taken care of ASAP!" - * // Bing Zapcrackle: "I'm on it my prince." * ``` * * @category DmsObject */ -/* istanbul ignore next */ -export async function getDmsObjectNotes(context: DvelopContext, params: GetDmsObjectNotesParams): Promise { - return await _getDmsObjectNotesFactory(_defaultHttpRequestFunction, _getDmsObjectNotesDefaultTransformFunction)(context, params); +export async function getDmsObjectNotes(context: DvelopContext, params: GetDmsObjectNotesParams): Promise; +export async function getDmsObjectNotes(context: DvelopContext, params: GetDmsObjectNotesParams, options: DvelopOptions): Promise; +export async function getDmsObjectNotes( + context: DvelopContext, + params: GetDmsObjectNotesParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}/n`, { method: "GET" }, options); } diff --git a/packages/dms/src/dms-objects/get-dms-object/get-dms-object.spec.ts b/packages/dms/src/dms-objects/get-dms-object/get-dms-object.spec.ts index bab5108c..ae31c237 100644 --- a/packages/dms/src/dms-objects/get-dms-object/get-dms-object.spec.ts +++ b/packages/dms/src/dms-objects/get-dms-object/get-dms-object.spec.ts @@ -1,31 +1,39 @@ -import { DmsObjectNote, DvelopContext, getDmsObjectMainFile, getDmsObjectNotes, getDmsObjectPdfFile, searchDmsObjects, SearchDmsObjectsResultPage } from "../../index"; -import { HttpResponse } from "../../utils/http"; -import { GetDmsObjectParams, _getDmsObjectFactory, _getDmsObjectDefaultTransformFunction, DmsObject } from "../get-dms-object/get-dms-object"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { + DmsObject, + GetDmsObjectParams, + onResponse, + getDmsObject, +} from "./get-dms-object"; +import { fetchDmsObjectFile, getDmsObjectMainFile, getDmsObjectPdfFile } from "../get-dms-object-file/get-dms-object-file"; +import { getDmsObjectNotes } from "../get-dms-object-notes/get-dms-object-notes"; +import { searchDmsObjects } from "../search-dms-objects/search-dms-objects"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); jest.mock("../get-dms-object-file/get-dms-object-file"); -jest.mock("../search-dms-objects/search-dms-objects"); jest.mock("../get-dms-object-notes/get-dms-object-notes"); +jest.mock("../search-dms-objects/search-dms-objects"); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; +const mockFetchDmsObjectFile = fetchDmsObjectFile as jest.MockedFunction; const mockGetDmsObjectMainFile = getDmsObjectMainFile as jest.MockedFunction; const mockGetDmsObjectPdfFile = getDmsObjectPdfFile as jest.MockedFunction; -const mockSearchDmsObjects = searchDmsObjects as jest.MockedFunction; const mockGetDmsObjectNotes = getDmsObjectNotes as jest.MockedFunction; +const mockSearchDmsObjects = searchDmsObjects as jest.MockedFunction; describe("getDmsObject", () => { - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); - let context: DvelopContext; let params: GetDmsObjectParams; beforeEach(() => { - jest.resetAllMocks(); - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { repositoryId: "HiItsMeRepositoryId", sourceId: "HiItsMeSourceId", @@ -33,216 +41,115 @@ describe("getDmsObject", () => { }; }); - it("should make correct request", async () => { - - const getDmsObject = _getDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method GET and a default onResponse", async () => { await getDmsObject(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping"], - templates: { - "repositoryid": params.repositoryId, - "sourceid": params.sourceId, - "dmsobjectid": params.dmsObjectId - } - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}?sourceid=${params.sourceId}`, + { method: "GET" }, + expect.objectContaining({ onResponse: expect.any(Function) }) + ); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const getDmsObject = _getDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction); - await getDmsObject(context, params); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await getDmsObject(context, params, options); - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + expect(mockDvelopFetch).toHaveBeenCalledWith(context, `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}?sourceid=${params.sourceId}`, { method: "GET" }, options); }); - describe("getDmsObjectDefaultTransformFunction", () => { - - it("should map correctly on no file links", async () => { - - const data: any = { - "_links": { - "HiItsMeLink": { - "href": "HiItsMeLinkHref" - }, - - }, - "id": "HiItsMeId", - "sourceProperties": [ - { - "key": "HiItsMeSourcePropertyId1", - "value": "HiItsMeSourcePropertyValue1" - }, - { - "key": "HiItsMeSourcePropertyId2", - "value": "HiItsMeSourcePropertyValue2" - } + describe("onResponse", () => { + + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { + status: 200, + headers: { "Content-Type": "application/json" } + }); + } + + it("should map base fields and not set any link-functions when no relevant links are present", async () => { + const data = { + _links: { HiItsMeLink: { href: "HiItsMeLinkHref" } }, + sourceProperties: [ + { key: "HiItsMeSourcePropertyId1", value: "HiItsMeSourcePropertyValue1" }, + { key: "HiItsMeSourcePropertyId2", value: "HiItsMeSourcePropertyValue2" } ], - "sourceCategories": [ - "HiItsMeSourceCategoriyId1", - "HiItsMeSourceCategoriyId2" - ] + sourceCategories: ["HiItsMeSourceCategoriyId1", "HiItsMeSourceCategoriyId2"] }; - const response: HttpResponse = { data: data } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - - const getDmsObject = _getDmsObjectFactory(mockHttpRequestFunction, _getDmsObjectDefaultTransformFunction); - const result: DmsObject = await getDmsObject(context, params); - - expect(result).toHaveProperty("repositoryId", params.repositoryId); - expect(result).toHaveProperty("sourceId", params.sourceId); - expect(result).toHaveProperty("dmsObjectId", params.dmsObjectId); - expect(result).toHaveProperty("properties", data["sourceProperties"]); - expect(result).toHaveProperty("categories", data["sourceCategories"]); + const result = await onResponse(context, params)(jsonResponse(data)) as DmsObject; - - expect(result).not.toHaveProperty("getMainFile"); - expect(result).not.toHaveProperty("getPdfFile"); + expect(result).toEqual({ + repositoryId: params.repositoryId, + sourceId: params.sourceId, + dmsObjectId: params.dmsObjectId, + properties: data.sourceProperties, + categories: data.sourceCategories + }); + expect(result.getMainFile).toBeUndefined(); + expect(result.getPdfFile).toBeUndefined(); + expect(result.searchChildren).toBeUndefined(); + expect(result.getNotes).toBeUndefined(); }); - it("should set getMainFile correctly", async () => { - - const data: any = { - "_links": { - "mainblobcontent": { - "href": "HiItsMeMainBlobContentHref" - }, - }, - "id": "HiItsMeId", - "sourceProperties": [], - "sourceCategories": [] - }; - - const response: HttpResponse = { data: data } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - const mainFile: ArrayBuffer = new ArrayBuffer(42); - mockGetDmsObjectMainFile.mockResolvedValue(mainFile); + it("should set getMainFile when mainblobcontent link present and call fetchDmsObjectFile with href", async () => { + const data = { _links: { mainblobcontent: { href: "HiItsMeMainBlobContentHref" } }, sourceProperties: [], sourceCategories: [] }; + const mainFile = new ArrayBuffer(42); + mockFetchDmsObjectFile.mockResolvedValue(mainFile); - const getDmsObject = _getDmsObjectFactory(mockHttpRequestFunction, _getDmsObjectDefaultTransformFunction); - const result: DmsObject = await getDmsObject(context, params); + const result = await onResponse(context, params)(jsonResponse(data)); expect(result.getMainFile).toEqual(expect.any(Function)); - - // @ts-ignore: Object is possibly 'null'. - const resultFile: ArrayBuffer = await result.getMainFile(); - expect(mockGetDmsObjectMainFile).toHaveBeenCalledTimes(1); - expect(mockGetDmsObjectMainFile).toHaveBeenCalledWith(context, params); + const resultFile = await result.getMainFile!(); + expect(mockFetchDmsObjectFile).toHaveBeenCalledWith(context, data._links.mainblobcontent.href); expect(resultFile).toBe(mainFile); }); - it("should set getPdfFile correctly", async () => { - - const data: any = { - "_links": { - "pdfblobcontent": { - "href": "HiItsMePdfBlobContentHref" - }, - }, - "id": "HiItsMeId", - "sourceProperties": [], - "sourceCategories": [] - }; + it("should set getPdfFile when pdfblobcontent link present and call fetchDmsObjectFile with href", async () => { + const data = { _links: { pdfblobcontent: { href: "HiItsMePdfBlobContentHref" } }, sourceProperties: [], sourceCategories: [] }; + const pdfFile = new ArrayBuffer(42); + mockFetchDmsObjectFile.mockResolvedValue(pdfFile); - const response: HttpResponse = { data: data } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - const pdfFile: ArrayBuffer = new ArrayBuffer(42); - mockGetDmsObjectPdfFile.mockResolvedValue(pdfFile); - - const getDmsObject = _getDmsObjectFactory(mockHttpRequestFunction, _getDmsObjectDefaultTransformFunction); - const result: DmsObject = await getDmsObject(context, params); + const result = await onResponse(context, params)(jsonResponse(data)); expect(result.getPdfFile).toEqual(expect.any(Function)); - - // @ts-ignore: Object is possibly 'null'. - const resultFile: ArrayBuffer = await result.getPdfFile(); - expect(mockGetDmsObjectPdfFile).toHaveBeenCalledTimes(1); - expect(mockGetDmsObjectPdfFile).toHaveBeenCalledWith(context, params); + const resultFile = await result.getPdfFile!(); + expect(mockFetchDmsObjectFile).toHaveBeenCalledWith(context, data._links.pdfblobcontent.href); expect(resultFile).toBe(pdfFile); }); - it("should set getChildren correctly", async () => { - - const data: any = { - "_links": { - "children": { - "href": "HiItsMeChildrenHref" - }, - }, - "id": "HiItsMeId", - "sourceProperties": [], - "sourceCategories": [] - }; - - const response: HttpResponse = { data: data } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - const searchResultPage: SearchDmsObjectsResultPage = { - page: 42, - dmsObjects: [] - } - mockSearchDmsObjects.mockResolvedValue(searchResultPage); + it("should set searchChildren when children link present and delegate to searchDmsObjects", async () => { + const data = { _links: { children: { href: "x" } }, sourceProperties: [], sourceCategories: [] }; + const searchResult = { page: 42, dmsObjects: [] }; + mockSearchDmsObjects.mockResolvedValue(searchResult); - const getDmsObject = _getDmsObjectFactory(mockHttpRequestFunction, _getDmsObjectDefaultTransformFunction); - const result: DmsObject = await getDmsObject(context, params); + const result = await onResponse(context, params)(jsonResponse(data)); expect(result.searchChildren).toEqual(expect.any(Function)); - - - // @ts-ignore: Object is possibly 'null'. - const resultChildren: SearchDmsObjectsResultPage = await result.searchChildren(); - expect(mockSearchDmsObjects).toHaveBeenCalledTimes(1); + const resultChildren = await result.searchChildren!(); expect(mockSearchDmsObjects).toHaveBeenCalledWith(context, { repositoryId: params.repositoryId, sourceId: params.sourceId, childrenOf: params.dmsObjectId }); - expect(resultChildren).toBe(searchResultPage); + expect(resultChildren).toBe(searchResult); }); - it("should set getNotes correctly", async () => { - - const data: any = { - "_links": { - "notes": { - "href": "HiItsMeNotedHref" - }, - }, - "id": "HiItsMeId", - "sourceProperties": [], - "sourceCategories": [] - }; - - const response: HttpResponse = { data: data } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - - const notes: DmsObjectNote[] = [{ + it("should set getNotes when notes link present and delegate to getDmsObjectNotes", async () => { + const data = { _links: { notes: { href: "x" } }, sourceProperties: [], sourceCategories: [] }; + const notes = [{ created: new Date(), - creator: { - id: "HiItsMeCreatorId", - displayName: "HiItsMeCreatorDisplayName" - }, + creator: { id: "HiItsMeCreatorId", displayName: "HiItsMeCreatorDisplayName" }, text: "HiItsMeNoteText" }]; mockGetDmsObjectNotes.mockResolvedValue(notes); - const getDmsObject = _getDmsObjectFactory(mockHttpRequestFunction, _getDmsObjectDefaultTransformFunction); - const result: DmsObject = await getDmsObject(context, params); + const result = await onResponse(context, params)(jsonResponse(data)); expect(result.getNotes).toEqual(expect.any(Function)); - - - // @ts-ignore: Object is possibly 'null'. - const resultNotes: DmsObjectNote[] = await result.getNotes(); - expect(mockGetDmsObjectNotes).toHaveBeenCalledTimes(1); + const resultNotes = await result.getNotes!(); expect(mockGetDmsObjectNotes).toHaveBeenCalledWith(context, params); expect(resultNotes).toBe(notes); }); diff --git a/packages/dms/src/dms-objects/get-dms-object/get-dms-object.ts b/packages/dms/src/dms-objects/get-dms-object/get-dms-object.ts index c39e05d7..76acce43 100644 --- a/packages/dms/src/dms-objects/get-dms-object/get-dms-object.ts +++ b/packages/dms/src/dms-objects/get-dms-object/get-dms-object.ts @@ -1,5 +1,8 @@ -import { DvelopContext, SearchDmsObjectsResultPage, getDmsObjectMainFile, getDmsObjectPdfFile, SearchDmsObjectsParams, searchDmsObjects, DmsObjectNote, getDmsObjectNotes } from "../../index"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; +import { DmsObjectNote, getDmsObjectNotes } from "../get-dms-object-notes/get-dms-object-notes"; +import { fetchDmsObjectFile } from "../get-dms-object-file/get-dms-object-file"; +import { searchDmsObjects, SearchDmsObjectsResultPage } from "../search-dms-objects/search-dms-objects"; /** * Parameters for the {@link getDmsObject}-function. @@ -12,7 +15,6 @@ export interface GetDmsObjectParams { sourceId: string; /** ID of the DmsObject */ dmsObjectId: string; - /** Short description of changes */ } /** @@ -51,85 +53,51 @@ export interface DmsObject { } /** - * Factory for the default-transform-function for the {@link getDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * Default `onResponse` provided to the {@link getDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category DmsObject */ -export function _getDmsObjectDefaultTransformFunctionFactory( - getDmsObjectMainFileFunction: (context: DvelopContext, params: GetDmsObjectParams) => Promise, - getDmsObjectPdfFileFunction: (context: DvelopContext, params: GetDmsObjectParams) => Promise, - searchDmsObjects: (context: DvelopContext, params: SearchDmsObjectsParams) => Promise, -) { - return (response: HttpResponse, context: DvelopContext, params: GetDmsObjectParams) => { +export function onResponse( + context: DvelopContext, + params: GetDmsObjectParams +): (response: Response) => Promise { + return async (response: Response) => { + await ensureSuccessResponse(response); + const data: any = await response.json(); + console.log("getDmsObject response data", data); const dmsObject: DmsObject = { repositoryId: params.repositoryId, sourceId: params.sourceId, dmsObjectId: params.dmsObjectId, - categories: response.data.sourceCategories, - properties: response.data.sourceProperties + categories: data.sourceCategories, + properties: data.sourceProperties }; - if (response.data._links.mainblobcontent) { - dmsObject.getMainFile = async () => (await getDmsObjectMainFileFunction(context, params)); + if (data._links?.mainblobcontent) { + dmsObject.getMainFile = () => fetchDmsObjectFile(context, data._links.mainblobcontent.href); } - if (response.data._links.pdfblobcontent) { - dmsObject.getPdfFile = async () => (await getDmsObjectPdfFileFunction(context, params)); + if (data._links?.pdfblobcontent) { + dmsObject.getPdfFile = () => fetchDmsObjectFile(context, data._links.pdfblobcontent.href); } - if (response.data._links.children) { - dmsObject.searchChildren = async () => (await searchDmsObjects(context, { + if (data._links?.children) { + dmsObject.searchChildren = () => searchDmsObjects(context, { repositoryId: params.repositoryId, sourceId: params.sourceId, childrenOf: params.dmsObjectId - })); + }); } - if (response.data._links.notes) { - dmsObject.getNotes = async () => (await getDmsObjectNotes(context, params)); + if (data._links?.notes) { + dmsObject.getNotes = () => getDmsObjectNotes(context, params); } return dmsObject; }; } -/** - * Factory for {@link getDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link getDmsObject}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category DmsObject - */ -export function _getDmsObjectFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: GetDmsObjectParams) => T -): (context: DvelopContext, params: GetDmsObjectParams) => Promise { - return async (context: DvelopContext, params: GetDmsObjectParams) => { - - const response: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping"], - templates: { - "repositoryid": params.repositoryId, - "sourceid": params.sourceId, - "dmsobjectid": params.dmsObjectId - } - }); - return transformFunction(response, context, params); - }; -} - -/** - * Factory for the default-transform-function for the {@link getDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @internal - * @category DmsObject - */ -/* istanbul ignore next */ -export async function _getDmsObjectDefaultTransformFunction(response: HttpResponse, context: DvelopContext, params: GetDmsObjectParams) { - return _getDmsObjectDefaultTransformFunctionFactory(getDmsObjectMainFile, getDmsObjectPdfFile, searchDmsObjects)(response, context, params); -} - /** * Get a DmsObject. * @@ -149,7 +117,14 @@ export async function _getDmsObjectDefaultTransformFunction(response: HttpRespon * ``` * @category DmsObject */ -/* istanbul ignore next */ -export async function getDmsObject(context: DvelopContext, params: GetDmsObjectParams) { - return _getDmsObjectFactory(_defaultHttpRequestFunction, _getDmsObjectDefaultTransformFunction)(context, params); +export async function getDmsObject(context: DvelopContext, params: GetDmsObjectParams): Promise; +export async function getDmsObject(context: DvelopContext, params: GetDmsObjectParams, options: DvelopOptions): Promise; +export async function getDmsObject( + context: DvelopContext, + params: GetDmsObjectParams, + options: DvelopOptions = { + onResponse: onResponse(context, params) + } +): Promise { + return dvelopFetch(context, `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}?sourceid=${params.sourceId}`, { method: "GET" }, options); } diff --git a/packages/dms/src/dms-objects/link-dms-objects/link-dms-objects.spec.ts b/packages/dms/src/dms-objects/link-dms-objects/link-dms-objects.spec.ts index 9ec645fd..105efa9b 100644 --- a/packages/dms/src/dms-objects/link-dms-objects/link-dms-objects.spec.ts +++ b/packages/dms/src/dms-objects/link-dms-objects/link-dms-objects.spec.ts @@ -1,65 +1,55 @@ -import { DvelopContext, DvelopHttpResponse } from "@dvelop-sdk/core"; -import { LinkDmsObjectsParams, _linkDmsObjectsFactory } from "./link-dms-objects"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { + LinkDmsObjectsParams, + onResponse, + linkDmsObjects, +} from "./link-dms-objects"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; describe("linkDmsObjects", () => { - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); let context: DvelopContext; let params: LinkDmsObjectsParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { repositoryId: "HiItsMeRepositoryId", sourceId: "HiItsMeSourceId", parentDmsObjectId: "HiItsMeDmsObjectId", - childDmsObjectsIds: [ - "HiItsMeChildDmsObjectId1", - "HiItsMeChildDmsObjectId2", - "HiItsMeChildDmsObjectId3" - ] + childDmsObjectsIds: ["HiItsMeChildDmsObjectId1", "HiItsMeChildDmsObjectId2"] }; }); - it("should make correct request", async () => { - - const linkDmsObjects = _linkDmsObjectsFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method POST and dmsObjectIds body", async () => { await linkDmsObjects(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "POST", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping", "linkDmsObject"], - templates: { - "repositoryid": params.repositoryId, - "sourceid": params.sourceId, - "dmsobjectid": params.parentDmsObjectId, - }, - data: { - "dmsObjectIds": params.childDmsObjectsIds - } - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe(`/dms/r/${params.repositoryId}/o2m/${params.parentDmsObjectId}/children`); + expect(calledInit).toMatchObject({ method: "POST" }); + expect(JSON.parse(calledInit!.body as string)).toEqual({ dmsObjectIds: params.childDmsObjectsIds }); + expect(calledOptions).toMatchObject({ onResponse: onResponse }); }); - it("should pass response to transform and return transform-result", async () => { - - const response: DvelopHttpResponse = {} as DvelopHttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const linkDmsObjects = _linkDmsObjectsFactory(mockHttpRequestFunction, mockTransformFunction); - await linkDmsObjects(context, params); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await linkDmsObjects(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + describe("_linkDmsObjectsDefaultTransformFunction", () => { + it("should resolve to undefined on 2xx", async () => { + const response = new Response(null, { status: 204 }); + await expect(onResponse(response)).resolves.toBeUndefined(); + }); }); }); diff --git a/packages/dms/src/dms-objects/link-dms-objects/link-dms-objects.ts b/packages/dms/src/dms-objects/link-dms-objects/link-dms-objects.ts index 6de44639..e195113d 100644 --- a/packages/dms/src/dms-objects/link-dms-objects/link-dms-objects.ts +++ b/packages/dms/src/dms-objects/link-dms-objects/link-dms-objects.ts @@ -1,5 +1,5 @@ -import { DvelopContext, DvelopHttpRequestConfig, DvelopHttpResponse } from "@dvelop-sdk/core"; -import { _defaultHttpRequestFunction } from "../../internal"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; /** * Parameters for the {@link linkDmsObjects}-function. @@ -17,33 +17,12 @@ export interface LinkDmsObjectsParams { } /** - * Factory for {@link linkDmsObjects}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link linkDmsObjects}-function. A corresponding transformFunction has to be supplied. + * Default transform-function provided to the {@link linkDmsObjects}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category DmsObject */ -export function _linkDmsObjectsFactory( - httpRequestFunction: (context: DvelopContext, config: DvelopHttpRequestConfig) => Promise, - transformFunction: (response: DvelopHttpResponse, context: DvelopContext, params: LinkDmsObjectsParams) => T -): (context: DvelopContext, params: LinkDmsObjectsParams) => Promise { - return async (context: DvelopContext, params: LinkDmsObjectsParams) => { - - const response: DvelopHttpResponse = await httpRequestFunction(context, { - method: "POST", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping", "linkDmsObject"], - templates: { - "repositoryid": params.repositoryId, - "sourceid": params.sourceId, - "dmsobjectid": params.parentDmsObjectId - }, - data: { - dmsObjectIds: params.childDmsObjectsIds - } - }) - - return transformFunction(response, context, params); - } +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } /** @@ -59,16 +38,22 @@ export function _linkDmsObjectsFactory( * repositoryId: "qnydFmqHuVo", * sourceId: "/dms/r/qnydFmqHuVo/source", * parentDmsObjectId: "GDYQ3PJKrT8", - * childDmsObjectsIds: [ - * "N3bEh-PEk1g", - * "AC86VI0j85M" - * ] + * childDmsObjectsIds: ["N3bEh-PEk1g", "AC86VI0j85M"] * }); - * * ``` * @category DmsObject */ -/* istanbul ignore next */ -export async function linkDmsObjects(context: DvelopContext, params: LinkDmsObjectsParams): Promise { - return _linkDmsObjectsFactory(_defaultHttpRequestFunction, () => { })(context, params); -} \ No newline at end of file +export async function linkDmsObjects(context: DvelopContext, params: LinkDmsObjectsParams): Promise; +export async function linkDmsObjects(context: DvelopContext, params: LinkDmsObjectsParams, options: DvelopOptions): Promise; +export async function linkDmsObjects( + context: DvelopContext, + params: LinkDmsObjectsParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/dms/r/${params.repositoryId}/o2m/${params.parentDmsObjectId}/children`, { + method: "POST", + body: JSON.stringify({ dmsObjectIds: params.childDmsObjectsIds }) + }, options); +} diff --git a/packages/dms/src/dms-objects/release-and-update-dms-objects/release-and-update-dms-objects.spec.ts b/packages/dms/src/dms-objects/release-and-update-dms-objects/release-and-update-dms-objects.spec.ts index 46ed5171..be229c42 100644 --- a/packages/dms/src/dms-objects/release-and-update-dms-objects/release-and-update-dms-objects.spec.ts +++ b/packages/dms/src/dms-objects/release-and-update-dms-objects/release-and-update-dms-objects.spec.ts @@ -1,18 +1,25 @@ -import { DvelopContext, UpdateDmsObjectParams, DmsObject } from "../../index"; -import { _releaseAndUpdateDmsObjectFactory, ReleaseAndUpdateDmsObjectError } from "./release-and-update-dms-objects"; +import { DvelopContext } from "@dvelop-sdk/core"; +import { DmsError } from "../../utils/dms-error"; +import { releaseAndUpdateDmsObject } from "./release-and-update-dms-objects"; +import { DmsObject, getDmsObject } from "../get-dms-object/get-dms-object"; +import { updateDmsObject, UpdateDmsObjectParams } from "../update-dms-object/update-dms-object"; +import { updateDmsObjectStatus } from "../update-dms-object-status/update-dms-object-status"; -describe("updateDmsObject", () => { +jest.mock("../get-dms-object/get-dms-object"); +jest.mock("../update-dms-object/update-dms-object"); +jest.mock("../update-dms-object-status/update-dms-object-status"); - const mockGetDmsObject = jest.fn(); - const mockUpdateDmsObjectStatus = jest.fn(); - const mockUpdateDmsObject = jest.fn(); +const mockGetDmsObject = getDmsObject as jest.MockedFunction; +const mockUpdateDmsObject = updateDmsObject as jest.MockedFunction; +const mockUpdateDmsObjectStatus = updateDmsObjectStatus as jest.MockedFunction; + +describe("releaseAndUpdateDmsObject", () => { let dmsObject: DmsObject; let context: DvelopContext; let params: UpdateDmsObjectParams; beforeEach(() => { - jest.resetAllMocks(); dmsObject = { @@ -20,18 +27,13 @@ describe("updateDmsObject", () => { sourceId: "hiItsMeSourceId", dmsObjectId: "hiItsMeDmsObjectId", categories: ["hiItsMeCategoryId"], - properties: [{ - key: "property_state", - value: "HiItsMeStatus" - }, { - key: "hiItsMeProperty", - value: "hiItsMePropertyValue" - }] + properties: [ + { key: "property_state", value: "HiItsMeStatus" }, + { key: "hiItsMeProperty", value: "hiItsMePropertyValue" } + ] }; - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { repositoryId: "HiItsMeRepositoryId", @@ -42,10 +44,8 @@ describe("updateDmsObject", () => { }); it("should call getDmsObject correctly", async () => { - mockGetDmsObject.mockResolvedValue(dmsObject); - const releaseAndUpdateDmsObject = _releaseAndUpdateDmsObjectFactory(mockGetDmsObject, mockUpdateDmsObjectStatus, mockUpdateDmsObject); await releaseAndUpdateDmsObject(context, params); expect(mockGetDmsObject).toHaveBeenCalledTimes(1); @@ -54,14 +54,11 @@ describe("updateDmsObject", () => { dmsObjectId: params.dmsObjectId, sourceId: params.sourceId }); - }); it("should call updateDmsObjectStatus correctly if state is not 'Released'", async () => { - mockGetDmsObject.mockResolvedValue(dmsObject); - const releaseAndUpdateDmsObject = _releaseAndUpdateDmsObjectFactory(mockGetDmsObject, mockUpdateDmsObjectStatus, mockUpdateDmsObject); await releaseAndUpdateDmsObject(context, params); expect(mockUpdateDmsObjectStatus).toHaveBeenCalledTimes(1); @@ -73,38 +70,24 @@ describe("updateDmsObject", () => { }); }); - it("should not call updateDmsObjectStatus if state is 'Released'", async () => { - mockGetDmsObject.mockResolvedValue({ ...dmsObject, - ...{ - properties: [ - { - key: "property_state", - value: "Released" - } - ] - } + properties: [{ key: "property_state", value: "Released" }] }); - const releaseAndUpdateDmsObject = _releaseAndUpdateDmsObjectFactory(mockGetDmsObject, mockUpdateDmsObjectStatus, mockUpdateDmsObject); await releaseAndUpdateDmsObject(context, params); - expect(mockUpdateDmsObjectStatus).toHaveBeenCalledTimes(0); + expect(mockUpdateDmsObjectStatus).not.toHaveBeenCalled(); }); - - it("should call updateDmsObject correctly", async () => { - mockGetDmsObject.mockResolvedValue(dmsObject); - const releaseAndUpdateDmsObject = _releaseAndUpdateDmsObjectFactory(mockGetDmsObject, mockUpdateDmsObjectStatus, mockUpdateDmsObject); await releaseAndUpdateDmsObject(context, params); expect(mockUpdateDmsObject).toHaveBeenCalledTimes(1); - expect(mockUpdateDmsObject).toHaveBeenCalledWith(context, params) + expect(mockUpdateDmsObject).toHaveBeenCalledWith(context, params); }); describe("handle no state", () => { @@ -115,35 +98,24 @@ describe("updateDmsObject", () => { sourceId: "hiItsMeSourceId", dmsObjectId: "hiItsMeDmsObjectId", categories: ["hiItsMeCategoryId"] - }, { + }, + { repositoryId: "someRepoId", sourceId: "hiItsMeSourceId", dmsObjectId: "hiItsMeDmsObjectId", categories: ["hiItsMeCategoryId"], - properties: [ - { - key: "hiItsMePropertyKey", - value: "hiItsMePropertyKey" - } - ] - }, { + properties: [{ key: "hiItsMePropertyKey", value: "hiItsMePropertyKey" }] + }, + { repositoryId: "someRepoId", sourceId: "hiItsMeSourceId", dmsObjectId: "hiItsMeDmsObjectId", categories: ["hiItsMeCategoryId"], - properties: [ - { - key: "property_state" - } - ] + properties: [{ key: "property_state" }] } - - ].forEach(testCase => { - it("should throw ReleaseAndUpdateDmsObjectError on no state", async () => { - - mockGetDmsObject.mockResolvedValue(testCase); - - const releaseAndUpdateDmsObject = _releaseAndUpdateDmsObjectFactory(mockGetDmsObject, mockUpdateDmsObjectStatus, mockUpdateDmsObject); + ].forEach((testCase, i) => { + it(`should throw DmsError on no state (case ${i})`, async () => { + mockGetDmsObject.mockResolvedValue(testCase as DmsObject); let expectedError: any; try { @@ -152,9 +124,9 @@ describe("updateDmsObject", () => { expectedError = error; } - expect(expectedError instanceof ReleaseAndUpdateDmsObjectError).toBeTruthy(); + expect(expectedError).toBeInstanceOf(DmsError); expect(expectedError.message).toContain("State of DmsObject could not be determined."); }); }); }); -}); \ No newline at end of file +}); diff --git a/packages/dms/src/dms-objects/release-and-update-dms-objects/release-and-update-dms-objects.ts b/packages/dms/src/dms-objects/release-and-update-dms-objects/release-and-update-dms-objects.ts index 4e5e5be4..309cd094 100644 --- a/packages/dms/src/dms-objects/release-and-update-dms-objects/release-and-update-dms-objects.ts +++ b/packages/dms/src/dms-objects/release-and-update-dms-objects/release-and-update-dms-objects.ts @@ -1,54 +1,8 @@ -import { BadInputError, DvelopContext } from "@dvelop-sdk/core"; -import { getDmsObject, GetDmsObjectParams, DmsObject, updateDmsObjectStatus, UpdateDmsObjectStatusParams, updateDmsObject, UpdateDmsObjectParams } from "../../index"; -import { _defaultHttpRequestFunction, _getDmsObjectFactory, _getDmsObjectDefaultTransformFunctionFactory, _updateDmsObjectDefaultTransformFunction } from "../../internal"; - -/** -* This error indicates a problem with reading the DmsObject. Do you have all relevant permissions? -* @category Error -*/ -export class ReleaseAndUpdateDmsObjectError extends Error { - // eslint-disable-next-line no-unused-vars - constructor(message: string) { - super(message); - Object.setPrototypeOf(this, ReleaseAndUpdateDmsObjectError.prototype); - } -} - -/** - * Factory for the {@link releaseAndUpdateDmsObject}-function. This is a wrapper around {@link getDmsObject}, {@link updateDmsObjectStatus} and {@link updateDmsObject} combined. - * @internal - * @category DmsObject - */ -export function _releaseAndUpdateDmsObjectFactory( - getDmsObjectsFunction: (context: DvelopContext, params: GetDmsObjectParams) => Promise, - updateDmsObjectStatusFunction: (context: DvelopContext, params: UpdateDmsObjectStatusParams) => Promise, - updateDmsObjectFunction: (context: DvelopContext, params: UpdateDmsObjectParams) => Promise, -): (context: DvelopContext, params: UpdateDmsObjectParams) => Promise { - - return async (context: DvelopContext, params: UpdateDmsObjectParams) => { - - const dmsObject: DmsObject = await getDmsObjectsFunction(context, { - repositoryId: params.repositoryId, - dmsObjectId: params.dmsObjectId, - sourceId: params.sourceId - }); - - const state: string | undefined = dmsObject.properties?.find(p => p.key === "property_state")?.value; - - if (!state) { - throw new ReleaseAndUpdateDmsObjectError("State of DmsObject could not be determined.") - } else if (state !== "Released") { - await updateDmsObjectStatusFunction(context, { - repositoryId: params.repositoryId, - dmsObjectId: params.dmsObjectId, - status: "Release", - alterationText: params.alterationText - }); - } - - return updateDmsObjectFunction(context, params); - } -} +import { DvelopContext } from "@dvelop-sdk/core"; +import { DmsError } from "../../utils/dms-error"; +import { getDmsObject } from "../get-dms-object/get-dms-object"; +import { updateDmsObject, UpdateDmsObjectParams } from "../update-dms-object/update-dms-object"; +import { updateDmsObjectStatus } from "../update-dms-object-status/update-dms-object-status"; /** * Release a DmsObject and update it. This is a variation of {@link updateDmsObject} which has the same syntax. @@ -59,36 +13,30 @@ export function _releaseAndUpdateDmsObjectFactory( * - {@link updateDmsObjectStatus} * - {@link updateDmsObject} * - * ```typescript - * import { updateDmsObject } from "@dvelop-sdk/dms"; - * import { readFileSync } from "fs"; - * - * //only node.js - * const file: ArrayBuffer = readFileSync(`${ __dirname }/our-profits.kaching`).buffer; - * - * await releaseAndUpdateDmsObject({ - * systemBaseUri: "https://steamwheedle-cartel.d-velop.cloud", - * authSessionId: "dQw4w9WgXcQ" - * }, { - * repositoryId: "qnydFmqHuVo", - * sourceId: "/dms/r/qnydFmqHuVo/source", - * dmsObjectId: "GDYQ3PJKrT8", - * alterationText: "Updated by SDK", - * properties: [ - * { - * key: "AaGK-fj-BAM", - * values: ["paid"] - * } - * ], - * fileName: "our-profits.kaching", - * content: file, - * alterationText: "Released for automatic update" - * }); - * ``` - * * @category DmsObject */ -/* istanbul ignore next */ -export function releaseAndUpdateDmsObject(context: DvelopContext, params: UpdateDmsObjectParams): Promise { - return _releaseAndUpdateDmsObjectFactory(getDmsObject, updateDmsObjectStatus, updateDmsObject)(context, params); +export async function releaseAndUpdateDmsObject(context: DvelopContext, params: UpdateDmsObjectParams): Promise { + + const dmsObject = await getDmsObject(context, { + repositoryId: params.repositoryId, + dmsObjectId: params.dmsObjectId, + sourceId: params.sourceId + }); + + const state = dmsObject.properties?.find(p => p.key === "property_state")?.value; + + if (!state) { + throw new DmsError("State of DmsObject could not be determined."); + } + + if (state !== "Released") { + await updateDmsObjectStatus(context, { + repositoryId: params.repositoryId, + dmsObjectId: params.dmsObjectId, + status: "Release", + alterationText: params.alterationText + }); + } + + return updateDmsObject(context, params); } diff --git a/packages/dms/src/dms-objects/search-dms-objects/search-dms-objects.spec.ts b/packages/dms/src/dms-objects/search-dms-objects/search-dms-objects.spec.ts index f27ec1b6..076c6597 100644 --- a/packages/dms/src/dms-objects/search-dms-objects/search-dms-objects.spec.ts +++ b/packages/dms/src/dms-objects/search-dms-objects/search-dms-objects.spec.ts @@ -1,384 +1,162 @@ -import { DvelopContext } from "../../index"; -import { HttpResponse } from "../../utils/http"; -import { _searchDmsObjectsDefaultTransformFunctionFactory, searchDmsObjectsFactory, SearchDmsObjectsParams, SearchDmsObjectsResultPage } from "./search-dms-objects"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { + SearchDmsObjectsParams, + SearchDmsObjectsResultPage, + onResponseFactory, + searchDmsObjects, +} from "./search-dms-objects"; -describe("searchDmsObjects", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("searchDmsObjects", () => { let context: DvelopContext; let params: SearchDmsObjectsParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - - params = { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; + params = { repositoryId: "HiItsMeRepositoryId", sourceId: "HiItsMeSourceId" }; }); - [ - { - params: { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId" - }, expectedTemplates: { - "repositoryid": "HiItsMeRepositoryId", - "sourceid": "HiItsMeSourceId" - } - }, - { - params: { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId", - categories: ["HiItsMeCategory1", "HiItsMeCategory2"] - }, expectedTemplates: { - "repositoryid": "HiItsMeRepositoryId", - "sourceid": "HiItsMeSourceId", - "sourcecategories": ["HiItsMeCategory1", "HiItsMeCategory2"], - } - }, - { - params: { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId", - properties: [ - - { - key: "HiItsMeProperty1", - values: ["HiItsMeProperty1Value1"] - }, { - key: "HiItsMeProperty2", - values: ["HiItsMeProperty2Value1", "HiItsMeProperty2Value2"] - } - ] - }, expectedTemplates: { - "repositoryid": "HiItsMeRepositoryId", - "sourceid": "HiItsMeSourceId", - "sourceproperties": { - "HiItsMeProperty1": ["HiItsMeProperty1Value1"], - "HiItsMeProperty2": ["HiItsMeProperty2Value1", "HiItsMeProperty2Value2"] - } - } - }, - { - params: { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId", - properties: [ - - { - key: "HiItsMeProperty1", - values: ["HiItsMeProperty1Value1"] - }, { - key: "HiItsMeProperty1", - values: ["HiItsMeProperty1Value2", "HiItsMeProperty1Value3"] - } - ] - }, expectedTemplates: { - "repositoryid": "HiItsMeRepositoryId", - "sourceid": "HiItsMeSourceId", - "sourceproperties": { - "HiItsMeProperty1": ["HiItsMeProperty1Value1", "HiItsMeProperty1Value2", "HiItsMeProperty1Value3"] - } - } - }, - { - params: { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId", - sortProperty: "HiItsMeSortProperty" - }, expectedTemplates: { - "repositoryid": "HiItsMeRepositoryId", - "sourceid": "HiItsMeSourceId", - "sourcepropertysort": "HiItsMeSortProperty", - } - }, - { - params: { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId", - ascending: true - }, expectedTemplates: { - "repositoryid": "HiItsMeRepositoryId", - "sourceid": "HiItsMeSourceId", - "ascending": true, - } - }, - { - params: { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId", - fulltext: "Hi Its Me Fulltext" - }, expectedTemplates: { - "repositoryid": "HiItsMeRepositoryId", - "sourceid": "HiItsMeSourceId", - "fulltext": "Hi Its Me Fulltext", - } - }, - { - params: { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId", - page: 42 - }, expectedTemplates: { - "repositoryid": "HiItsMeRepositoryId", - "sourceid": "HiItsMeSourceId", - "page": 42, - } - }, - { - params: { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId", - pageSize: 42 - }, expectedTemplates: { - "repositoryid": "HiItsMeRepositoryId", - "sourceid": "HiItsMeSourceId", - "pagesize": 42, - } - }, - { - params: { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId", - childrenOf: "HiItsMeParentDmsObjectId" - }, expectedTemplates: { - "repositoryid": "HiItsMeRepositoryId", - "sourceid": "HiItsMeSourceId", - "children_of": "HiItsMeParentDmsObjectId", - } - } - ].forEach(testCase => { - it(`should make correct request for params: ${JSON.stringify(testCase.params)}`, async () => { - - const searchDmsObjects = searchDmsObjectsFactory(mockHttpRequestFunction, mockTransformFunction); - await searchDmsObjects(context, testCase.params); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/dms", - follows: ["repo", "searchresultwithmapping"], - templates: testCase.expectedTemplates - }); - }); - }); - - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const searchDmsObjects = searchDmsObjectsFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method GET", async () => { await searchDmsObjects(context, params); - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + `/dms/r/${params.repositoryId}/srm`, + { method: "GET" }, + expect.objectContaining({ onResponse: expect.any(Function) }) + ); }); - describe("getDmsObjectDefaultTransformFunction", () => { + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await searchDmsObjects(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - it("should set page", async () => { + describe("_searchDmsObjectsDefaultTransformFunctionFactory", () => { - const data: any = { - "page": 3, - "items": [] - }; - - const response: HttpResponse = { data: data } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } - const searchDmsObjects = searchDmsObjectsFactory(mockHttpRequestFunction, _searchDmsObjectsDefaultTransformFunctionFactory(mockHttpRequestFunction)); - const result: SearchDmsObjectsResultPage = await searchDmsObjects(context, params); + it("should set page", async () => { + const transform = onResponseFactory(context, params); + const result = await transform(jsonResponse({ page: 3, items: [] })); expect(result).toHaveProperty("page", 3); - expect(result).not.toHaveProperty("getPreviousPage"); - expect(result).not.toHaveProperty("getNextPage"); + expect(result.getPreviousPage).toBeUndefined(); + expect(result.getNextPage).toBeUndefined(); }); - describe("items", () => { - it("should transform items", async () => { - - const data: any = { - "page": 3, - "items": [{ - "id": "HiItsMeDmsObjectId1", - "sourceProperties": [ - { - "key": "HiItsMeProperty1Key", - "value": "HiItsMeProperty1Value", - "isMultiValue": false - }, - { - "key": "HiItsMeProperty2Key", - "value": "HiItsMeProperty2Value", - "isMultiValue": true, - "values": { - "1": "HiItsMeProperty2Value1", - "2": "HiItsMeProperty2Value2", - } - } + it("should transform items", async () => { + const data = { + page: 3, + items: [ + { + id: "HiItsMeDmsObjectId1", + sourceProperties: [ + { key: "HiItsMeProperty1Key", value: "HiItsMeProperty1Value", isMultiValue: false } ], - "sourceCategories": [ - "HiItsMeCategory1", - "HiItsMeCategory2" - ] + sourceCategories: ["HiItsMeCategory1"] }, { - "id": "HiItsMeDmsObjectId2", - "sourceProperties": [ - ], - "sourceCategories": ["HiItsMeCategory1"] + id: "HiItsMeDmsObjectId2", + sourceProperties: [], + sourceCategories: ["HiItsMeCategory1"] } - ] - }; - - const response: HttpResponse = { data: data } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - - const searchDmsObjects = searchDmsObjectsFactory(mockHttpRequestFunction, _searchDmsObjectsDefaultTransformFunctionFactory(mockHttpRequestFunction)); - const result: SearchDmsObjectsResultPage = await searchDmsObjects(context, params); - - expect(result.dmsObjects.length).toBe(2); - expect(result.dmsObjects[0]).toHaveProperty("repositoryId", params.repositoryId); - expect(result.dmsObjects[0]).toHaveProperty("sourceId", params.sourceId); - expect(result.dmsObjects[0]).toHaveProperty("dmsObjectId", data.items[0].id); - expect(result.dmsObjects[0]).toHaveProperty("properties", data.items[0].sourceProperties); - expect(result.dmsObjects[0]).toHaveProperty("categories", data.items[0].sourceCategories); - - expect(result.dmsObjects[1]).toHaveProperty("repositoryId", params.repositoryId); - expect(result.dmsObjects[1]).toHaveProperty("sourceId", params.sourceId); - expect(result.dmsObjects[1]).toHaveProperty("dmsObjectId", data.items[1].id); - expect(result.dmsObjects[1]).toHaveProperty("categories", data.items[1].sourceCategories); - - - expect(result.dmsObjects[0]).not.toHaveProperty("getMainFile"); - expect(result.dmsObjects[1]).not.toHaveProperty("getMainFile"); - }); - - it("should set getMainFile Function", async () => { - - const data: any = { - "page": 3, - "items": [{ - "_links": { - "mainblobcontent": { - "href": "HiItsMeMainBlobContentHref" - } - }, - "id": "HiItsMeDmsObjectId1", - "sourceProperties": [ - ], - "sourceCategories": ["HiItsMeCategory1",] - }] - }; - - const response: HttpResponse = { data: data } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - - const searchDmsObjects = searchDmsObjectsFactory(mockHttpRequestFunction, _searchDmsObjectsDefaultTransformFunctionFactory(mockHttpRequestFunction)); - const result: SearchDmsObjectsResultPage = await searchDmsObjects(context, params); - - expect(result.dmsObjects[0]).toHaveProperty("getMainFile"); + ] + }; - const file: ArrayBuffer = new ArrayBuffer(42); - mockHttpRequestFunction.mockResolvedValue({ data: file }); - const fileResult: ArrayBuffer = await result.dmsObjects[0].getMainFile(); + const transform = onResponseFactory(context, params); + const result = await transform(jsonResponse(data)); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "HiItsMeMainBlobContentHref", - headers: { "Accept": "application/octet-stream" }, - responseType: "arraybuffer" - }); - expect(fileResult).toEqual(file); + expect(result.dmsObjects).toHaveLength(2); + expect(result.dmsObjects[0]).toMatchObject({ + repositoryId: params.repositoryId, + sourceId: params.sourceId, + dmsObjectId: "HiItsMeDmsObjectId1", + properties: data.items[0].sourceProperties, + categories: data.items[0].sourceCategories }); + expect(result.dmsObjects[0].getMainFile).toBeUndefined(); }); - it("should set getPreviousPage", async () => { - - const data: any = { - "_links": { - "prev": { - "href": "HiItsMePreviousHref" - } - }, - "page": 3, - "items": [] + it("should set getMainFile when mainblobcontent link present and call dvelopFetch on invocation", async () => { + const data = { + page: 3, + items: [{ + _links: { mainblobcontent: { href: "HiItsMeMainBlobContentHref" } }, + id: "HiItsMeDmsObjectId1", + sourceProperties: [], + sourceCategories: ["HiItsMeCategory1"] + }] }; - const response: HttpResponse = { data: data } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); + const transform = onResponseFactory(context, params); + const result = await transform(jsonResponse(data)); - const searchDmsObjects = searchDmsObjectsFactory(mockHttpRequestFunction, _searchDmsObjectsDefaultTransformFunctionFactory(mockHttpRequestFunction)); - const result: SearchDmsObjectsResultPage = await searchDmsObjects(context, params); + expect(result.dmsObjects[0].getMainFile).toEqual(expect.any(Function)); - expect(result).toHaveProperty("getPreviousPage"); + const href = "HiItsMeMainBlobContentHref"; + mockDvelopFetch.mockResolvedValueOnce(href); + mockDvelopFetch.mockResolvedValueOnce(new ArrayBuffer(42)); + await result.dmsObjects[0].getMainFile!(); - const prevData: any = { - "_links": {}, - "page": 2, - "items": [] - }; + expect(mockDvelopFetch).toHaveBeenLastCalledWith( + context, + href, + { method: "GET", headers: { "Accept": "application/octet-stream" } }, + expect.objectContaining({ onResponse: expect.any(Function) }) + ); + }); - mockHttpRequestFunction.mockResolvedValue({ data: prevData }); - const prevResult: SearchDmsObjectsResultPage = await result.getPreviousPage(); + it("should set getPreviousPage when prev link present", async () => { + const data = { _links: { prev: { href: "HiItsMePreviousHref" } }, page: 3, items: [] }; - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: data._links.prev.href - }); - expect(prevResult).toHaveProperty("page", 2); - expect(prevResult).toHaveProperty("dmsObjects", []); - expect(prevResult).not.toHaveProperty("getPreviousPage"); - }); + const transform = onResponseFactory(context, params); + const result = await transform(jsonResponse(data)); - it("should set getNextPage", async () => { + expect(result.getPreviousPage).toEqual(expect.any(Function)); - const data: any = { - "_links": { - "next": { - "href": "HiItsMeNextHref" - } - }, - "page": 3, - "items": [] - }; + const prevResultData = { page: 2, items: [] }; + mockDvelopFetch.mockResolvedValueOnce(prevResultData as unknown as SearchDmsObjectsResultPage); + await result.getPreviousPage!(); - const response: HttpResponse = { data: data } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); + expect(mockDvelopFetch).toHaveBeenLastCalledWith( + context, + { href: "HiItsMePreviousHref" }, + { method: "GET" }, + expect.objectContaining({ onResponse: expect.any(Function) }) + ); + }); - const searchDmsObjects = searchDmsObjectsFactory(mockHttpRequestFunction, _searchDmsObjectsDefaultTransformFunctionFactory(mockHttpRequestFunction)); - const result: SearchDmsObjectsResultPage = await searchDmsObjects(context, params); + it("should set getNextPage when next link present", async () => { + const data = { _links: { next: { href: "HiItsMeNextHref" } }, page: 3, items: [] }; - expect(result).toHaveProperty("getNextPage"); + const transform = onResponseFactory(context, params); + const result = await transform(jsonResponse(data)); - const nextData: any = { - "_links": {}, - "page": 2, - "items": [] - }; + expect(result.getNextPage).toEqual(expect.any(Function)); - mockHttpRequestFunction.mockResolvedValue({ data: nextData }); - const nextResult: SearchDmsObjectsResultPage = await result.getNextPage(); + const nextResultData = { page: 4, items: [] }; + mockDvelopFetch.mockResolvedValueOnce(nextResultData as unknown as SearchDmsObjectsResultPage); + await result.getNextPage!(); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: data._links.next.href - }); - expect(nextResult).toHaveProperty("page", 2); - expect(nextResult).toHaveProperty("dmsObjects", []); - expect(nextResult).not.toHaveProperty("getNextPage"); + expect(mockDvelopFetch).toHaveBeenLastCalledWith( + context, + { href: "HiItsMeNextHref" }, + { method: "GET" }, + expect.objectContaining({ onResponse: expect.any(Function) }) + ); }); }); -}); \ No newline at end of file +}); diff --git a/packages/dms/src/dms-objects/search-dms-objects/search-dms-objects.ts b/packages/dms/src/dms-objects/search-dms-objects/search-dms-objects.ts index 22b70519..d4ca4228 100644 --- a/packages/dms/src/dms-objects/search-dms-objects/search-dms-objects.ts +++ b/packages/dms/src/dms-objects/search-dms-objects/search-dms-objects.ts @@ -1,5 +1,6 @@ -import { DmsObject, DvelopContext } from "../../index"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; +import { getDmsObjectMainFile } from "../get-dms-object-file/get-dms-object-file"; /** * Parameters for the {@link searchDmsObjects}-function. @@ -29,7 +30,6 @@ export interface SearchDmsObjectsParams { * @category DmsObject */ export interface ListedDmsObject { - /** ID of the repository */ repositoryId: string; /** ID of the source */ @@ -73,132 +73,53 @@ export interface SearchDmsObjectsResultPage { * @internal * @category DmsObject */ -function _listedDmsObjectDefaultTransformFunctionFactory(httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise): (dto: any, context: DvelopContext, params: SearchDmsObjectsParams) => ListedDmsObject { - return (dto: any, context: DvelopContext, params: SearchDmsObjectsParams) => { - - const result: ListedDmsObject = { - repositoryId: params.repositoryId, - sourceId: params.sourceId, - dmsObjectId: dto.id, - categories: dto.sourceCategories, - properties: dto.sourceProperties - }; - - if (dto._links?.mainblobcontent) { - result.getMainFile = async () => { - const mainBlobContentResponse = await httpRequestFunction(context, { - method: "GET", - url: dto._links.mainblobcontent.href, - headers: { "Accept": "application/octet-stream" }, - responseType: "arraybuffer" - }); - return mainBlobContentResponse.data; - }; - } - - return result; - }; -} +export function onResponseFactory( + context: DvelopContext, + params: SearchDmsObjectsParams +): (response: Response) => Promise { + return async (response: Response) => { -/** - * Factory for the default-transform-function for the {@link searchDmsObjects}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @internal - * @category DmsObject - */ -export function _searchDmsObjectsDefaultTransformFunctionFactory(httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise): (response: HttpResponse, context: DvelopContext, params: SearchDmsObjectsParams) => SearchDmsObjectsResultPage { - return (response: HttpResponse, context: DvelopContext, params: SearchDmsObjectsParams) => { + await ensureSuccessResponse(response); + const data: any = await response.json(); const result: SearchDmsObjectsResultPage = { - page: response.data.page, - dmsObjects: response.data.items.map((item: any) => _listedDmsObjectDefaultTransformFunctionFactory(httpRequestFunction)(item, context, params)) - }; - - if (response.data._links?.prev) { - result.getPreviousPage = async () => { - const prevResponse: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: response.data._links.prev.href - }); - return _searchDmsObjectsDefaultTransformFunctionFactory(httpRequestFunction)(prevResponse, context, params); - }; - } - - if (response.data._links?.next) { - result.getNextPage = async () => { - const nextResponse: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: response.data._links.next.href - }); - return _searchDmsObjectsDefaultTransformFunctionFactory(httpRequestFunction)(nextResponse, context, params); - }; + page: data.page, + dmsObjects: data.items.map((item: any) => { + const result: ListedDmsObject = { + repositoryId: params.repositoryId, + sourceId: params.sourceId, + dmsObjectId: item.id, + categories: item.sourceCategories, + properties: item.sourceProperties + }; + + if (item._links?.mainblobcontent) { + result.getMainFile = async () => { + return getDmsObjectMainFile(context, { + repositoryId: params.repositoryId, + sourceId: params.sourceId, + dmsObjectId: item.id + }) + } + } + + return result; + }) } - return result; - }; -} - -function formatProperties(properties: { key: string, values: string[] }[]): { [key: string]: string[] } { - - const sourceProperties: { [key: string]: string[] } = {}; - properties.forEach(p => { - if (sourceProperties[p.key]) { - sourceProperties[p.key] = sourceProperties[p.key].concat(p.values); - } else { - sourceProperties[p.key] = p.values; + if (data._links?.prev) { + result.getPreviousPage = async () => dvelopFetch(context, data._links.prev, { method: "GET" }, { + onResponse: onResponseFactory(context, params) + }); } - }); - return sourceProperties; -} - -/** - * Factory for the {@link searchDmsObjects}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link storeFileFunction}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category DmsObject - */ -export function searchDmsObjectsFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: SearchDmsObjectsParams) => T -): (context: DvelopContext, params: SearchDmsObjectsParams) => Promise { - return async (context: DvelopContext, params: SearchDmsObjectsParams) => { - const templates: { [key: string]: any } = { - "repositoryid": params.repositoryId, - "sourceid": params.sourceId, - }; - if (params.categories) { - templates["sourcecategories"] = params.categories; - } - if (params.properties) { - templates["sourceproperties"] = formatProperties(params.properties); - } - if (params.sortProperty) { - templates["sourcepropertysort"] = params.sortProperty; - } - if (params.ascending) { - templates["ascending"] = params.ascending; + if (data._links?.next) { + result.getNextPage = async () => dvelopFetch(context, data._links.next, { method: "GET" }, { + onResponse: onResponseFactory(context, params) + }); } - if (params.fulltext) { - templates["fulltext"] = params.fulltext; - } - if (params.page) { - templates["page"] = params.page; - } - if (params.pageSize) { - templates["pagesize"] = params.pageSize; - } - if (params.childrenOf) { - templates["children_of"] = params.childrenOf; - } - - const response: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: "/dms", - follows: ["repo", "searchresultwithmapping"], - templates: templates - }); - return transformFunction(response, context, params); + return result; }; } @@ -222,19 +143,17 @@ export function searchDmsObjectsFactory( * values: ["unpaid"] * }] * }); - * - * console.log(searchResult.dmsObjects.length); - * - * let dmsObjects: DmsObject[] = searchResult.dmsObjects - * - * while (searchResult.getNextPage) { // Don't call function here, just check existence - * const nextPage: SearchDmsObjectsResultPage = await searchResult.getNextPage(); - * dmsObjects = dmsObjects.concat(nextPage.dmsObjects); - * } * ``` * @category DmsObject */ -/* istanbul ignore next */ -export function searchDmsObjects(context: DvelopContext, params: SearchDmsObjectsParams): Promise { - return searchDmsObjectsFactory(_defaultHttpRequestFunction, _searchDmsObjectsDefaultTransformFunctionFactory(_defaultHttpRequestFunction))(context, params); -} \ No newline at end of file +export async function searchDmsObjects(context: DvelopContext, params: SearchDmsObjectsParams): Promise; +export async function searchDmsObjects(context: DvelopContext, params: SearchDmsObjectsParams, options: DvelopOptions): Promise; +export async function searchDmsObjects( + context: DvelopContext, + params: SearchDmsObjectsParams, + options: DvelopOptions = { + onResponse: onResponseFactory(context, params) + } +): Promise { + return dvelopFetch(context, `/dms/r/${params.repositoryId}/srm`, { method: "GET" }, options); +} diff --git a/packages/dms/src/dms-objects/store-file-temporarily/store-file-temporarily.spec.ts b/packages/dms/src/dms-objects/store-file-temporarily/store-file-temporarily.spec.ts index 146a8920..2d22a120 100644 --- a/packages/dms/src/dms-objects/store-file-temporarily/store-file-temporarily.spec.ts +++ b/packages/dms/src/dms-objects/store-file-temporarily/store-file-temporarily.spec.ts @@ -1,74 +1,61 @@ -import { DvelopContext } from "../../index"; -import { HttpResponse } from "../../utils/http"; -import { _storeFileTemporarilyDefaultTransformFunction, _storeFileTemporarilyFactory, StoreFileTemporarilyParams } from "./store-file-temporarily"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { + StoreFileTemporarilyParams, + onResponse, + storeFileTemporarily, +} from "./store-file-temporarily"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); -describe("storeFileTemporarilyFactory", () => { +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("storeFileTemporarily", () => { let context: DvelopContext; let params: StoreFileTemporarilyParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - - params = { - repositoryId: "HiItsMeRepositoryId", - content: new ArrayBuffer(42) - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; + params = { repositoryId: "HiItsMeRepositoryId", content: new ArrayBuffer(42) }; }); - it("should make correct request", async () => { - - const storeFileTemporarily = _storeFileTemporarilyFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method POST, octet-stream Content-Type and content body", async () => { await storeFileTemporarily(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe(`/dms/r/${params.repositoryId}/blob/chunk`); + expect(calledInit).toMatchObject({ method: "POST", - url: "/dms", - follows: ["repo", "chunkedupload"], - templates: { "repositoryid": params.repositoryId }, headers: { "Content-Type": "application/octet-stream" }, - data: params.content + body: params.content }); + expect(calledOptions).toMatchObject({ onResponse: onResponse }); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const storeFileTemporarily = _storeFileTemporarilyFactory(mockHttpRequestFunction, mockTransformFunction); - await storeFileTemporarily(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await storeFileTemporarily(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - describe("storeFileTemporarilyDefaultTransformFunction", () => { - - it("should map correctly", async () => { + describe("onResponse", () => { - const headers: any = { - "location": "HiItsmeLocation", - "some-header": "HiItsMeSomeHeader" - }; - - mockHttpRequestFunction.mockResolvedValue({ headers: headers } as HttpResponse); - - const storeFileTemporarily = _storeFileTemporarilyFactory(mockHttpRequestFunction, _storeFileTemporarilyDefaultTransformFunction); - const result: string = await storeFileTemporarily(context, params); + it("should return the location header", async () => { + const response = new Response(null, { status: 201, headers: { location: "HiItsMeLocation" } }); + const result = await onResponse(response); + expect(result).toEqual("HiItsMeLocation"); + }); - expect(result).toEqual(headers["location"]); + it("should return empty string when location is missing", async () => { + const response = new Response(null, { status: 201 }); + const result = await onResponse(response); + expect(result).toEqual(""); }); }); }); diff --git a/packages/dms/src/dms-objects/store-file-temporarily/store-file-temporarily.ts b/packages/dms/src/dms-objects/store-file-temporarily/store-file-temporarily.ts index 22aca600..63671b5a 100644 --- a/packages/dms/src/dms-objects/store-file-temporarily/store-file-temporarily.ts +++ b/packages/dms/src/dms-objects/store-file-temporarily/store-file-temporarily.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "../../index"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; /** * Parameters for the {@link storeFileTemporarily}-function. @@ -17,31 +17,9 @@ export interface StoreFileTemporarilyParams { * @internal * @category DmsObject */ -export function _storeFileTemporarilyDefaultTransformFunction(response: HttpResponse, _: DvelopContext, __: StoreFileTemporarilyParams): string { - return response.headers["location"] || ""; -} - -/** - * Factory for the {@link storeFileFunction}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link storeFileFunction}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category DmsObject - */ -export function _storeFileTemporarilyFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: StoreFileTemporarilyParams) => T -): (context: DvelopContext, params: StoreFileTemporarilyParams) => Promise { - return async (context: DvelopContext, params: StoreFileTemporarilyParams) => { - const response: HttpResponse = await httpRequestFunction(context, { - method: "POST", - url: "/dms", - follows: ["repo", "chunkedupload"], - templates: { "repositoryid": params.repositoryId }, - headers: { "Content-Type": "application/octet-stream" }, - data: params.content - }); - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + return response.headers.get("location") ?? ""; } /** @@ -67,7 +45,18 @@ export function _storeFileTemporarilyFactory( * * @category DmsObject */ -/* istanbul ignore next */ -export async function storeFileTemporarily(context: DvelopContext, params: StoreFileTemporarilyParams): Promise { - return _storeFileTemporarilyFactory(_defaultHttpRequestFunction, _storeFileTemporarilyDefaultTransformFunction)(context, params); -} \ No newline at end of file +export async function storeFileTemporarily(context: DvelopContext, params: StoreFileTemporarilyParams): Promise; +export async function storeFileTemporarily(context: DvelopContext, params: StoreFileTemporarilyParams, options: DvelopOptions): Promise; +export async function storeFileTemporarily( + context: DvelopContext, + params: StoreFileTemporarilyParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/dms/r/${params.repositoryId}/blob/chunk`, { + method: "POST", + headers: { "Content-Type": "application/octet-stream" }, + body: params.content + }, options); +} diff --git a/packages/dms/src/dms-objects/unlink-dms-objects/unlink-dms-objects.spec.ts b/packages/dms/src/dms-objects/unlink-dms-objects/unlink-dms-objects.spec.ts index b54a0dda..85bd103d 100644 --- a/packages/dms/src/dms-objects/unlink-dms-objects/unlink-dms-objects.spec.ts +++ b/packages/dms/src/dms-objects/unlink-dms-objects/unlink-dms-objects.spec.ts @@ -1,21 +1,25 @@ -import { DvelopContext, DvelopHttpResponse } from "@dvelop-sdk/core"; -import { UnlinkDmsObjectsParams, _unlinkDmsObjectsFactory } from "./unlink-dms-objects"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { + UnlinkDmsObjectsParams, + onResponse, + unlinkDmsObjects, +} from "./unlink-dms-objects"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; describe("unlinkDmsObjects", () => { - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); let context: DvelopContext; let params: UnlinkDmsObjectsParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { repositoryId: "HiItsMeRepositoryId", sourceId: "HiItsMeSourceId", @@ -24,32 +28,28 @@ describe("unlinkDmsObjects", () => { }; }); - it("should make correct request", async () => { - - const unlinkDmsObjects = _unlinkDmsObjectsFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method DELETE", async () => { await unlinkDmsObjects(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "DELETE", - url: `/dms/r/${params.repositoryId}/o2m/${params.parentDmsObjectId}/children/${params.childDmsObjectsId}`, - params: { - "sourceid": params.sourceId - } - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + `/dms/r/${params.repositoryId}/o2m/${params.parentDmsObjectId}/children/${params.childDmsObjectsId}`, + { method: "DELETE" }, + expect.objectContaining({ onResponse: onResponse }) + ); }); - it("should pass response to transform and return transform-result", async () => { - - const response: DvelopHttpResponse = {} as DvelopHttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const unlinkDmsObjects = _unlinkDmsObjectsFactory(mockHttpRequestFunction, mockTransformFunction); - await unlinkDmsObjects(context, params); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await unlinkDmsObjects(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + describe("_unlinkDmsObjectsDefaultTransformFunction", () => { + it("should resolve to undefined on 2xx", async () => { + const response = new Response(null, { status: 204 }); + await expect(onResponse(response)).resolves.toBeUndefined(); + }); }); }); diff --git a/packages/dms/src/dms-objects/unlink-dms-objects/unlink-dms-objects.ts b/packages/dms/src/dms-objects/unlink-dms-objects/unlink-dms-objects.ts index f4a10152..a7fdf8c0 100644 --- a/packages/dms/src/dms-objects/unlink-dms-objects/unlink-dms-objects.ts +++ b/packages/dms/src/dms-objects/unlink-dms-objects/unlink-dms-objects.ts @@ -1,5 +1,5 @@ -import { DvelopContext, DvelopHttpRequestConfig, DvelopHttpResponse } from "@dvelop-sdk/core"; -import { _defaultHttpRequestFunction } from "../../internal"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; /** * Parameters for the {@link unlinkDmsObjects}-function. @@ -17,27 +17,12 @@ export interface UnlinkDmsObjectsParams { } /** - * Factory for {@link unlinkDmsObjects}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link unlinkDmsObjects}-function. A corresponding transformFunction has to be supplied. + * Default transform-function provided to the {@link unlinkDmsObjects}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category DmsObject */ -export function _unlinkDmsObjectsFactory( - httpRequestFunction: (context: DvelopContext, config: DvelopHttpRequestConfig) => Promise, - transformFunction: (response: DvelopHttpResponse, context: DvelopContext, params: UnlinkDmsObjectsParams) => T -): (context: DvelopContext, params: UnlinkDmsObjectsParams) => Promise { - return async (context: DvelopContext, params: UnlinkDmsObjectsParams) => { - - const response: DvelopHttpResponse = await httpRequestFunction(context, { - method: "DELETE", - url: `/dms/r/${params.repositoryId}/o2m/${params.parentDmsObjectId}/children/${params.childDmsObjectsId}`, - params: { - "sourceid": params.sourceId - } - }) - - return transformFunction(response, context, params); - } +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } /** @@ -55,11 +40,17 @@ export function _unlinkDmsObjectsFactory( * parentDmsObjectId: "GDYQ3PJKrT8", * childDmsObjectsId: "N3bEh-PEk1g" * }); - * * ``` * @category DmsObject */ -/* istanbul ignore next */ -export async function unlinkDmsObjects(context: DvelopContext, params: UnlinkDmsObjectsParams): Promise { - return _unlinkDmsObjectsFactory(_defaultHttpRequestFunction, () => { })(context, params); -} \ No newline at end of file +export async function unlinkDmsObjects(context: DvelopContext, params: UnlinkDmsObjectsParams): Promise; +export async function unlinkDmsObjects(context: DvelopContext, params: UnlinkDmsObjectsParams, options: DvelopOptions): Promise; +export async function unlinkDmsObjects( + context: DvelopContext, + params: UnlinkDmsObjectsParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/dms/r/${params.repositoryId}/o2m/${params.parentDmsObjectId}/children/${params.childDmsObjectsId}`, { method: "DELETE" }, options); +} diff --git a/packages/dms/src/dms-objects/update-dms-object-status/update-dms-object-status.spec.ts b/packages/dms/src/dms-objects/update-dms-object-status/update-dms-object-status.spec.ts index f08f50b0..610664dc 100644 --- a/packages/dms/src/dms-objects/update-dms-object-status/update-dms-object-status.spec.ts +++ b/packages/dms/src/dms-objects/update-dms-object-status/update-dms-object-status.spec.ts @@ -1,112 +1,80 @@ -import { DvelopContext } from "../../index"; -import { HttpResponse } from "../../utils/http"; -import { _updateDmsObjectStatusDefaultTransformFunction, _updateDmsObjectStatusFactory, UpdateDmsObjectStatusParams } from "./update-dms-object-status"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { + UpdateDmsObjectStatusParams, + onResponse, + updateDmsObjectStatus, +} from "./update-dms-object-status"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); -describe("updateDmsObject", () => { +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("updateDmsObjectStatus", () => { let context: DvelopContext; let params: UpdateDmsObjectStatusParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { repositoryId: "HiItsMeRepositoryId", dmsObjectId: "HiItsMeDmsObjectId", - status: "Processing", + status: "Processing" }; }); - it("should make correct request", async () => { - const updateDmsObject = _updateDmsObjectStatusFactory(mockHttpRequestFunction, mockTransformFunction); - await updateDmsObject(context, params); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "PUT", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping", "displayVersion"], - templates: { - "repositoryid": params.repositoryId, - "dmsobjectid": params.dmsObjectId - }, - data: { - "sourceId": `/dms/r/${params.repositoryId}/source`, - "sourceProperties": { - "properties": [ - { key: "property_state", values: [params.status] } - ] - } + it("should call dvelopFetch with method PUT and a body containing property_state", async () => { + await updateDmsObjectStatus(context, params); + + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe(`/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}/v/current`); + expect(calledInit).toMatchObject({ method: "PUT" }); + expect(JSON.parse(calledInit!.body as string)).toEqual({ + sourceId: `/dms/r/${params.repositoryId}/source`, + sourceProperties: { + properties: [{ key: "property_state", values: ["Processing"] }] } }); + expect(calledOptions).toMatchObject({ onResponse: onResponse }); }); - it("should include editor if given", async () => { - - params.editor = "HiItsMeEditor" - - const updateDmsObject = _updateDmsObjectStatusFactory(mockHttpRequestFunction, mockTransformFunction); - await updateDmsObject(context, params); + it("should include property_editor when editor is set", async () => { + params.editor = "HiItsMeEditor"; + await updateDmsObjectStatus(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, expect.objectContaining({ - data: expect.objectContaining({ - "sourceProperties": expect.objectContaining({ - "properties": expect.arrayContaining([ - { key: "property_editor", values: [params.editor] } - ]) - }) - }) - })); + const calledInit = mockDvelopFetch.mock.calls[0][2]; + const body = JSON.parse(calledInit!.body as string); + expect(body.sourceProperties.properties).toEqual([ + { key: "property_state", values: ["Processing"] }, + { key: "property_editor", values: ["HiItsMeEditor"] } + ]); }); - it("should alterationtext if given", async () => { - + it("should include alterationText when set", async () => { params.alterationText = "HiItsMeAlterationText"; + await updateDmsObjectStatus(context, params); - const updateDmsObject = _updateDmsObjectStatusFactory(mockHttpRequestFunction, mockTransformFunction); - await updateDmsObject(context, params); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, expect.objectContaining({ - data: expect.objectContaining({ - "alterationText": params.alterationText - }) - })); + const calledInit = mockDvelopFetch.mock.calls[0][2]; + const body = JSON.parse(calledInit!.body as string); + expect(body.alterationText).toEqual("HiItsMeAlterationText"); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const updateDmsObject = _updateDmsObjectStatusFactory(mockHttpRequestFunction, mockTransformFunction); - await updateDmsObject(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await updateDmsObjectStatus(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - describe("updateDmsObjectDefaultTransformer", () => { - - it("should return void", async () => { - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - - const updateDmsObject = _updateDmsObjectStatusFactory(mockHttpRequestFunction, _updateDmsObjectStatusDefaultTransformFunction); - const result = await updateDmsObject(context, params); - - expect(result).toBe(undefined); + describe("_updateDmsObjectStatusDefaultTransformFunction", () => { + it("should resolve to undefined on 2xx", async () => { + const response = new Response(null, { status: 204 }); + await expect(onResponse(response)).resolves.toBeUndefined(); }); }); }); diff --git a/packages/dms/src/dms-objects/update-dms-object-status/update-dms-object-status.ts b/packages/dms/src/dms-objects/update-dms-object-status/update-dms-object-status.ts index 714a9d28..9b6f9f18 100644 --- a/packages/dms/src/dms-objects/update-dms-object-status/update-dms-object-status.ts +++ b/packages/dms/src/dms-objects/update-dms-object-status/update-dms-object-status.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "../../index"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; export type DmsObjectStatus = "Processing" | "Verification" | "Release"; @@ -25,54 +25,13 @@ export interface UpdateDmsObjectStatusParams { * @internal * @category DmsObject */ -export function _updateDmsObjectStatusDefaultTransformFunction(_: HttpResponse, __: DvelopContext, ___: UpdateDmsObjectStatusParams): void { } // no error indicates success. Returning void - -/** - * Factory for the {@link updateDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link updateDmsObjectStatus}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category DmsObject - */ -export function _updateDmsObjectStatusFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: UpdateDmsObjectStatusParams) => T, -): (context: DvelopContext, params: UpdateDmsObjectStatusParams) => Promise { - return async (context: DvelopContext, params: UpdateDmsObjectStatusParams) => { - - const properties: { key: string, values: string[] }[] = [{ - key: "property_state", - values: [params.status] - }] - - if (params.editor) { - properties.push({ - key: "property_editor", - values: [params.editor] - }) - } - - const response: HttpResponse = await httpRequestFunction(context, { - method: "PUT", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping", "displayVersion"], - templates: { - "repositoryid": params.repositoryId, - "dmsobjectid": params.dmsObjectId - }, - data: { - "sourceId": `/dms/r/${params.repositoryId}/source`, - "alterationText": params.alterationText || undefined, - "sourceProperties": { - "properties": properties - } - } - }); - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } + /** - * Update a DmsObject. + * Update a DmsObject's status. * * ```typescript * import { updateDmsObjectStatus } from "@dvelop-sdk/dms"; @@ -91,7 +50,26 @@ export function _updateDmsObjectStatusFactory( * * @category DmsObject */ -/* istanbul ignore next */ -export function updateDmsObjectStatus(context: DvelopContext, params: UpdateDmsObjectStatusParams): Promise { - return _updateDmsObjectStatusFactory(_defaultHttpRequestFunction, _updateDmsObjectStatusDefaultTransformFunction)(context, params); -} \ No newline at end of file +export async function updateDmsObjectStatus(context: DvelopContext, params: UpdateDmsObjectStatusParams): Promise; +export async function updateDmsObjectStatus(context: DvelopContext, params: UpdateDmsObjectStatusParams, options: DvelopOptions): Promise; +export async function updateDmsObjectStatus( + context: DvelopContext, + params: UpdateDmsObjectStatusParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}/v/current`, { + method: "PUT", + body: JSON.stringify({ + sourceId: `/dms/r/${params.repositoryId}/source`, + alterationText: params.alterationText, + sourceProperties: { + properties: [ + { key: "property_state", values: [params.status] }, + ...(params.editor ? [{ key: "property_editor", values: [params.editor] }] : []) + ] + } + }) + }, options); +} diff --git a/packages/dms/src/dms-objects/update-dms-object/update-dms-object.spec.ts b/packages/dms/src/dms-objects/update-dms-object/update-dms-object.spec.ts index 8874a404..9f37d623 100644 --- a/packages/dms/src/dms-objects/update-dms-object/update-dms-object.spec.ts +++ b/packages/dms/src/dms-objects/update-dms-object/update-dms-object.spec.ts @@ -1,28 +1,29 @@ -import { DvelopContext } from "../../index"; -import { HttpResponse } from "../../utils/http"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { + UpdateDmsObjectParams, + onResponse, + updateDmsObject, +} from "./update-dms-object"; import { storeFileTemporarily } from "../store-file-temporarily/store-file-temporarily"; -import { updateDmsObjectDefaultStoreFileFunction, _updateDmsObjectDefaultTransformFunction, _updateDmsObjectFactory, UpdateDmsObjectParams } from "./update-dms-object"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); jest.mock("../store-file-temporarily/store-file-temporarily"); -const mockStoryFileTemporarily = storeFileTemporarily as jest.MockedFunction; -describe("updateDmsObject", () => { +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; +const mockStoreFileTemporarily = storeFileTemporarily as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); - let mockStoreFileFunction = jest.fn(); +describe("updateDmsObject", () => { let context: DvelopContext; let params: UpdateDmsObjectParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { repositoryId: "HiItsMeRepositoryId", sourceId: "HiItsMeSourceId", @@ -30,127 +31,70 @@ describe("updateDmsObject", () => { alterationText: "HiItsMeAlterationText", categoryId: "HiItsMeCategoryId", properties: [ - { - key: "HiItsMeProperty1Key", - values: ["HiItsMeProperty1Value"] - }, - { - key: "HiItsMeProperty2Key", - values: ["HiItsMeProperty2Value1", "HiItsMeProperty2Value2"] - } + { key: "HiItsMeProperty1Key", values: ["HiItsMeProperty1Value"] }, + { key: "HiItsMeProperty2Key", values: ["HiItsMeProperty2Value1", "HiItsMeProperty2Value2"] } ] }; }); - it("should not call storeFileFunction if contentUri is given", async () => { - - params.contentUri = "HiItsMeContentUri"; - params.content = new ArrayBuffer(42); - - const updateDmsObject = _updateDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, mockStoreFileFunction); + it("should call dvelopFetch with method PUT and a JSON body", async () => { await updateDmsObject(context, params); - expect(mockStoreFileFunction).toHaveBeenCalledTimes(0); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe(`/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}`); + expect(calledInit).toMatchObject({ method: "PUT" }); + expect(JSON.parse(calledInit!.body as string)).toEqual({ + sourceId: params.sourceId, + alterationText: params.alterationText, + sourceCategory: params.categoryId, + sourceProperties: { properties: params.properties }, + fileName: params.fileName, + contentLocationUri: params.contentLocationUri, + contentUri: params.contentUri + }); + expect(calledOptions).toMatchObject({ onResponse: onResponse }); }); - it("should not call storeFileFunction if contentLocationUri is given", async () => { - - params.contentLocationUri = "HiItsMeContentUri"; + it("should not call storeFileTemporarily when contentUri is set", async () => { + params.contentUri = "HiItsMeContentUri"; params.content = new ArrayBuffer(42); - const updateDmsObject = _updateDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, mockStoreFileFunction); await updateDmsObject(context, params); - - expect(mockStoreFileFunction).toHaveBeenCalledTimes(0); + expect(mockStoreFileTemporarily).not.toHaveBeenCalled(); }); - it("should call storeFileFunction if content is given", async () => { - + it("should not call storeFileTemporarily when contentLocationUri is set", async () => { + params.contentLocationUri = "HiItsMeContentLocationUri"; params.content = new ArrayBuffer(42); - mockStoreFileFunction.mockReturnValue({ setAs: "contentUri", uri: "HiItsMeUri" }); - const updateDmsObject = _updateDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, mockStoreFileFunction); await updateDmsObject(context, params); - - expect(mockStoreFileFunction).toHaveBeenCalledTimes(1); - expect(mockStoreFileFunction).toHaveBeenCalledWith(context, params); + expect(mockStoreFileTemporarily).not.toHaveBeenCalled(); }); - it("should make correct request", async () => { + it("should call storeFileTemporarily and set contentLocationUri when content is given", async () => { + params.content = new ArrayBuffer(42); + mockStoreFileTemporarily.mockResolvedValue("HiItsMeTemporaryUri"); - const updateDmsObject = _updateDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, mockStoreFileFunction); await updateDmsObject(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "PUT", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping", "update"], - templates: { - "repositoryid": params.repositoryId, - "sourceid": params.sourceId, - "dmsobjectid": params.dmsObjectId - }, - data: { - "sourceId": params.sourceId, - "alterationText": params.alterationText, - "sourceCategory": params.categoryId, - "sourceProperties": { - "properties": params.properties - }, - "fileName": params.fileName, - "contentLocationUri": params.contentLocationUri, - "contentUri": params.contentUri - } - }); + expect(mockStoreFileTemporarily).toHaveBeenCalledWith(context, { repositoryId: params.repositoryId, content: params.content }); + const calledInit = mockDvelopFetch.mock.calls[0][2]; + expect(JSON.parse(calledInit!.body as string).contentLocationUri).toEqual("HiItsMeTemporaryUri"); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const updateDmsObject = _updateDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, mockStoreFileFunction); - await updateDmsObject(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await updateDmsObject(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - describe("updateDmsObjectDefaultTransformer", () => { - - it("should return void", async () => { - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - - const updateDmsObject = _updateDmsObjectFactory(mockHttpRequestFunction, _updateDmsObjectDefaultTransformFunction, mockStoreFileFunction); - const result = await updateDmsObject(context, params); - - expect(result).toBe(undefined); + describe("_updateDmsObjectDefaultTransformFunction", () => { + it("should resolve to undefined on 2xx", async () => { + const response = new Response(null, { status: 204 }); + await expect(onResponse(response)).resolves.toBeUndefined(); }); }); - describe("updateDmsObjectDefaultStoreFileFunction", () => { - - it("should call storeFileTemporarily correctly", async () => { - - const temporaryFileUrl = "HiItsMeTemporaryFileUrl"; - mockStoryFileTemporarily.mockResolvedValue(temporaryFileUrl); - params.content = new ArrayBuffer(42); - - const updateDmsObject = _updateDmsObjectFactory(mockHttpRequestFunction, mockTransformFunction, updateDmsObjectDefaultStoreFileFunction); - await updateDmsObject(context, params); - - expect(mockStoryFileTemporarily).toHaveBeenCalledTimes(1); - expect(mockStoryFileTemporarily).toHaveBeenCalledWith(context, params); - - expect(mockHttpRequestFunction).toHaveBeenCalledWith(expect.any(Object), expect.objectContaining({ - data: expect.objectContaining({ - contentLocationUri: temporaryFileUrl - }) - })); - }); - }); }); diff --git a/packages/dms/src/dms-objects/update-dms-object/update-dms-object.ts b/packages/dms/src/dms-objects/update-dms-object/update-dms-object.ts index d0322730..ba50869e 100644 --- a/packages/dms/src/dms-objects/update-dms-object/update-dms-object.ts +++ b/packages/dms/src/dms-objects/update-dms-object/update-dms-object.ts @@ -1,6 +1,6 @@ -import { DvelopContext } from "../../index"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; -import { storeFileTemporarily, StoreFileTemporarilyParams } from "../store-file-temporarily/store-file-temporarily"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; +import { storeFileTemporarily } from "../store-file-temporarily/store-file-temporarily"; /** * Parameters for the {@link updateDmsObject}-function. @@ -39,66 +39,12 @@ export interface UpdateDmsObjectParams { } /** - * Default transform-function provided to the {@link updateDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * Default `onResponse` provided to the {@link updateDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category DmsObject */ -export function _updateDmsObjectDefaultTransformFunction(_: HttpResponse, __: DvelopContext, ___: UpdateDmsObjectParams): void { } // no error indicates success. Returning void - -/** - * Default storeFile-function provided to the {@link updateDmsObject}-function. This will get called when content is provided. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @internal - * @category DmsObject - */ -export async function updateDmsObjectDefaultStoreFileFunction(context: DvelopContext, params: UpdateDmsObjectParams): Promise<{ setAs: "contentUri" | "contentLocationUri", uri: string }> { - const uri: string = await storeFileTemporarily(context, params as StoreFileTemporarilyParams); - return { - setAs: "contentLocationUri", - uri: uri - }; -} - -/** - * Factory for the {@link updateDmsObject}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link updateDmsObject}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category DmsObject - */ -export function _updateDmsObjectFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: UpdateDmsObjectParams) => T, - storeFileFunction: (context: DvelopContext, params: UpdateDmsObjectParams) => Promise<{ setAs: "contentUri" | "contentLocationUri", uri: string }> -): (context: DvelopContext, params: UpdateDmsObjectParams) => Promise { - return async (context: DvelopContext, params: UpdateDmsObjectParams) => { - - if (!params.contentUri && !params.contentLocationUri && params.content) { - const storedFileInfo: { setAs: "contentUri" | "contentLocationUri", uri: string } = await storeFileFunction(context, params); - params[storedFileInfo.setAs] = storedFileInfo.uri; - } - - const response: HttpResponse = await httpRequestFunction(context, { - method: "PUT", - url: "/dms", - follows: ["repo", "dmsobjectwithmapping", "update"], - templates: { - "repositoryid": params.repositoryId, - "sourceid": params.sourceId, - "dmsobjectid": params.dmsObjectId - }, - data: { - "sourceId": params.sourceId, - "alterationText": params.alterationText, - "sourceCategory": params.categoryId, - "sourceProperties": { - "properties": params.properties - }, - "fileName": params.fileName, - "contentLocationUri": params.contentLocationUri, - "contentUri": params.contentUri - } - }); - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } /** @@ -132,7 +78,32 @@ export function _updateDmsObjectFactory( * * @category DmsObject */ -/* istanbul ignore next */ -export function updateDmsObject(context: DvelopContext, params: UpdateDmsObjectParams): Promise { - return _updateDmsObjectFactory(_defaultHttpRequestFunction, _updateDmsObjectDefaultTransformFunction, updateDmsObjectDefaultStoreFileFunction)(context, params); -} \ No newline at end of file +export async function updateDmsObject(context: DvelopContext, params: UpdateDmsObjectParams): Promise; +export async function updateDmsObject(context: DvelopContext, params: UpdateDmsObjectParams, options: DvelopOptions): Promise; +export async function updateDmsObject( + context: DvelopContext, + params: UpdateDmsObjectParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + if (!params.contentUri && !params.contentLocationUri && params.content) { + params.contentLocationUri = await storeFileTemporarily(context, { + repositoryId: params.repositoryId, + content: params.content + }); + } + + return dvelopFetch(context, `/dms/r/${params.repositoryId}/o2m/${params.dmsObjectId}`, { + method: "PUT", + body: JSON.stringify({ + sourceId: params.sourceId, + alterationText: params.alterationText, + sourceCategory: params.categoryId, + sourceProperties: { "properties": params.properties }, + fileName: params.fileName, + contentLocationUri: params.contentLocationUri, + contentUri: params.contentUri + }) + }, options); +} diff --git a/packages/dms/src/index.ts b/packages/dms/src/index.ts index 54a3b48d..472dcf62 100644 --- a/packages/dms/src/index.ts +++ b/packages/dms/src/index.ts @@ -25,9 +25,8 @@ */ // Utils -export { DvelopContext, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError } from "@dvelop-sdk/core"; -export { DmsError } from "./utils/http"; -export * as internals from "./internal"; +export { DvelopContext, DvelopOptions, dvelopFetch, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError } from "@dvelop-sdk/core"; +export { DmsError } from "./utils/dms-error"; // Repository export { GetRepositoryParams, Repository, getRepository } from "./repositories/get-repository/get-repository"; @@ -44,7 +43,7 @@ export { UpdateDmsObjectParams, updateDmsObject } from "./dms-objects/update-dms export { UpdateDmsObjectStatusParams, updateDmsObjectStatus } from "./dms-objects/update-dms-object-status/update-dms-object-status"; export { DeleteCurrentDmsObjectVersionParams, deleteCurrentDmsObjectVersion } from "./dms-objects/delete-current-dms-object-version/delete-current-dms-object-version"; export { SearchDmsObjectsParams, SearchDmsObjectsResultPage, ListedDmsObject, searchDmsObjects } from "./dms-objects/search-dms-objects/search-dms-objects"; -export { ReleaseAndUpdateDmsObjectError, releaseAndUpdateDmsObject } from "./dms-objects/release-and-update-dms-objects/release-and-update-dms-objects"; +export { releaseAndUpdateDmsObject } from "./dms-objects/release-and-update-dms-objects/release-and-update-dms-objects"; export { LinkDmsObjectsParams, linkDmsObjects } from "./dms-objects/link-dms-objects/link-dms-objects"; export { UnlinkDmsObjectsParams, unlinkDmsObjects } from "./dms-objects/unlink-dms-objects/unlink-dms-objects"; diff --git a/packages/dms/src/internal.ts b/packages/dms/src/internal.ts deleted file mode 100644 index 15a83c30..00000000 --- a/packages/dms/src/internal.ts +++ /dev/null @@ -1,20 +0,0 @@ -export { _defaultHttpRequestFunctionFactory, _defaultHttpRequestFunction } from "./utils/http"; - -// Repository -export { _getRepositoryFactory, _getRepositoryDefaultTransformFunction } from "./repositories/get-repository/get-repository"; -export { _getRepositoriesFactory, _getRepositoriesDefaultTransformFunction } from "./repositories/get-repositories/get-repositories"; - -// DmsObjects -export { _getDmsObjectFactory, _getDmsObjectDefaultTransformFunction, _getDmsObjectDefaultTransformFunctionFactory } from "./dms-objects/get-dms-object/get-dms-object"; -export { _getDmsObjectMainFileFactory, _getDmsObjectPdfFileFactory } from "./dms-objects/get-dms-object-file/get-dms-object-file"; -export { _getDmsObjectNotesFactory, _getDmsObjectNotesDefaultTransformFunction } from "./dms-objects/get-dms-object-notes/get-dms-object-notes"; -export { _createDmsObjectDefaultStoreFileFunction, _createDmsObjectDefaultTransformFunction } from "./dms-objects/create-dms-object/create-dms-object"; -export { _createDmsObjectNoteFactory, _createDmsObjectNoteDefaultTransformFunction } from "./dms-objects/create-dms-object-note/create-dms-object-note"; -export { _storeFileTemporarilyFactory, _storeFileTemporarilyDefaultTransformFunction } from "./dms-objects/store-file-temporarily/store-file-temporarily"; -export { _updateDmsObjectFactory, _updateDmsObjectDefaultTransformFunction } from "./dms-objects/update-dms-object/update-dms-object"; -export { _updateDmsObjectStatusFactory, _updateDmsObjectStatusDefaultTransformFunction } from "./dms-objects/update-dms-object-status/update-dms-object-status"; -export { _deleteCurrentDmsObjectVersionFactory, _deleteCurrentDmsObjectVersionDefaultTransformFunction } from "./dms-objects/delete-current-dms-object-version/delete-current-dms-object-version"; -export { _searchDmsObjectsDefaultTransformFunctionFactory } from "./dms-objects/search-dms-objects/search-dms-objects"; -export { _releaseAndUpdateDmsObjectFactory } from "./dms-objects/release-and-update-dms-objects/release-and-update-dms-objects"; -export { _linkDmsObjectsFactory } from "./dms-objects/link-dms-objects/link-dms-objects"; -export { _unlinkDmsObjectsFactory } from "./dms-objects/unlink-dms-objects/unlink-dms-objects"; diff --git a/packages/dms/src/mapping/get-mappings/get-mappings.spec.ts b/packages/dms/src/mapping/get-mappings/get-mappings.spec.ts index 8732b9d1..ed037373 100644 --- a/packages/dms/src/mapping/get-mappings/get-mappings.spec.ts +++ b/packages/dms/src/mapping/get-mappings/get-mappings.spec.ts @@ -1,124 +1,87 @@ -import { DvelopContext } from "../../index"; -import { HttpResponse } from "../../utils/http"; -import { GetMappingsParams, _getDmsMappingFactory, _getDmsMappingsDefaultTransformFunction, DmsMapping } from "./get-mappings"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { + DmsMapping, + GetMappingsParams, + onResponse, + getMappings, +} from "./get-mappings"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); -describe("getMappings", () => { +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("getMappings", () => { let context: DvelopContext; let params: GetMappingsParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - - params = { - repositoryId: "HiItsMeRepositoryId", - sourceId: "HiItsMeSourceId" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; + params = { repositoryId: "HiItsMeRepositoryId", sourceId: "HiItsMeSourceId" }; }); - it("should make correct request", async () => { - - const getDmsObject = _getDmsMappingFactory(mockHttpRequestFunction, mockTransformFunction); - await getDmsObject(context, params); + it("should call dvelopFetch with method GET", async () => { + await getMappings(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/dms", - follows: ["repo", "mappingconfig"], - templates: { - "repositoryid": params.repositoryId, - "sourceid": params.sourceId, - } - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + `/dms/r/${params.repositoryId}/m?sourceId=${params.sourceId}`, + { method: "GET" }, + expect.objectContaining({ onResponse: onResponse }) + ); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const getMappings = _getDmsMappingFactory(mockHttpRequestFunction, mockTransformFunction); - await getMappings(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await getMappings(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - describe("getDmsMappingsDefaultTransformFunction", () => { + describe("_getDmsMappingsDefaultTransformFunction", () => { - it("should map correctly", async () => { + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } - const data: any = { - "mappings": [ + it("should map mappings correctly", async () => { + const data = { + mappings: [ { - "_links": { - "self": "Mapping1" - }, - "name": "My Test Mapping", - "sourceId": params.sourceId, - "mappingItems": [ - { - "destination": "dest1", - "source": "source1", - "type": 1 - }, - { - "destination": "dest2", - "source": "source2", - "type": 0 - } + name: "My Test Mapping", + sourceId: params.sourceId, + mappingItems: [ + { destination: "dest1", source: "source1", type: 1 }, + { destination: "dest2", source: "source2", type: 0 } ] }, { - "_links": { - "self": "Mapping2" - }, - "name": "My Other Mapping", - "sourceId": params.sourceId, - "mappingItems": [ - { - "destination": "dest1_1", - "source": "source1_1", - "type": 1 - }, + name: "My Other Mapping", + sourceId: params.sourceId, + mappingItems: [ + { destination: "dest1_1", source: "source1_1", type: 1 } ] } ] }; - const response: HttpResponse = { data: data } as HttpResponse; - mockHttpRequestFunction.mockResolvedValue(response); - - const getMappings = _getDmsMappingFactory(mockHttpRequestFunction, _getDmsMappingsDefaultTransformFunction); - const result: DmsMapping[] = await getMappings(context, params); + const result: DmsMapping[] = await onResponse(jsonResponse(data)); expect(result).toHaveLength(2); - - expect(result[0]).toHaveProperty("sourceId", params.sourceId); - expect(result[0]).toHaveProperty("name", "My Test Mapping"); - expect(result[0]).toHaveProperty("mappingItems[0].destination", "dest1"); - expect(result[0]).toHaveProperty("mappingItems[0].source", "source1"); - expect(result[0]).toHaveProperty("mappingItems[0].type", 1); - expect(result[0]).toHaveProperty("mappingItems[1].destination", "dest2"); - expect(result[0]).toHaveProperty("mappingItems[1].source", "source2"); - expect(result[0]).toHaveProperty("mappingItems[1].type", 0); - - expect(result[1]).toHaveProperty("sourceId", params.sourceId); - expect(result[1]).toHaveProperty("name", "My Other Mapping"); - expect(result[1]).toHaveProperty("mappingItems[0].destination", "dest1_1"); - expect(result[1]).toHaveProperty("mappingItems[0].source", "source1_1"); - expect(result[1]).toHaveProperty("mappingItems[0].type", 1); + expect(result[0]).toMatchObject({ + sourceId: params.sourceId, + name: "My Test Mapping", + mappingItems: data.mappings[0].mappingItems + }); + expect(result[1]).toMatchObject({ + sourceId: params.sourceId, + name: "My Other Mapping", + mappingItems: data.mappings[1].mappingItems + }); }); }); }); diff --git a/packages/dms/src/mapping/get-mappings/get-mappings.ts b/packages/dms/src/mapping/get-mappings/get-mappings.ts index 74050773..d98e4c9b 100644 --- a/packages/dms/src/mapping/get-mappings/get-mappings.ts +++ b/packages/dms/src/mapping/get-mappings/get-mappings.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "../../index"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; /** * Parameters for the {@link getMappings}-function. @@ -33,52 +33,22 @@ export interface DmsMapping { } /** - * Factory for the default-transform-function for the {@link getMappings}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @internal - * @category DmsObject - */ -export function _getDmsMappingDefaultTransformFunctionFactory() { - return (response: HttpResponse, _context: DvelopContext, _params: GetMappingsParams) => { - - const mappings: DmsMapping[] = [...response.data.mappings]; - - return mappings; - }; -} - -/** - * Factory for {@link getMappings}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link getMappings}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category Mappings - */ -export function _getDmsMappingFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: GetMappingsParams) => T -): (context: DvelopContext, params: GetMappingsParams) => Promise { - return async (context: DvelopContext, params: GetMappingsParams) => { - - const response: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: "/dms", - follows: ["repo", "mappingconfig"], - templates: { - "repositoryid": params.repositoryId, - "sourceid": params.sourceId, - } - }); - return transformFunction(response, context, params); - }; -} - -/** - * Factory for the default-transform-function for the {@link getMappings}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * Default transform-function provided to the {@link getMappings}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Mappings */ -/* istanbul ignore next */ -export async function _getDmsMappingsDefaultTransformFunction(response: HttpResponse, context: DvelopContext, params: GetMappingsParams) { - return _getDmsMappingDefaultTransformFunctionFactory()(response, context, params); +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + const data: any = await response.json(); + return data.mappings.map((m: any): DmsMapping => ({ + sourceId: m.sourceId, + name: m.name, + mappingItems: m.mappingItems.map((item: any) => ({ + destination: item.destination, + source: item.source, + type: item.type + })) + })); } /** @@ -99,7 +69,14 @@ export async function _getDmsMappingsDefaultTransformFunction(response: HttpResp * ``` * @category Mappings */ -/* istanbul ignore next */ -export async function getMappings(context: DvelopContext, params: GetMappingsParams) { - return _getDmsMappingFactory(_defaultHttpRequestFunction, _getDmsMappingsDefaultTransformFunction)(context, params); +export async function getMappings(context: DvelopContext, params: GetMappingsParams): Promise; +export async function getMappings(context: DvelopContext, params: GetMappingsParams, options: DvelopOptions): Promise; +export async function getMappings( + context: DvelopContext, + params: GetMappingsParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/dms/r/${params.repositoryId}/m?sourceId=${params.sourceId}`, { method: "GET" }, options); } diff --git a/packages/dms/src/repositories/get-repositories/get-repositories.spec.ts b/packages/dms/src/repositories/get-repositories/get-repositories.spec.ts index 3bbe710d..a3c6b341 100644 --- a/packages/dms/src/repositories/get-repositories/get-repositories.spec.ts +++ b/packages/dms/src/repositories/get-repositories/get-repositories.spec.ts @@ -1,113 +1,61 @@ -import { DvelopContext } from "../../index"; -import { HttpResponse } from "../../utils/http"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; import { Repository } from "../get-repository/get-repository"; -import { _getRepositoriesDefaultTransformFunction, _getRepositoriesFactory } from "./get-repositories"; +import { onResponse, getRepositories } from "./get-repositories"; -describe("getRepositoriesFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("getRepositories", () => { let context: DvelopContext; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; }); - it("should make correct request", async () => { - - const getRepositories = _getRepositoriesFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method GET", async () => { await getRepositories(context); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/dms", - follows: ["allrepos"], - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + "/dms/r", + { method: "GET" }, + expect.objectContaining({ onResponse: onResponse }) + ); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const getRepositories = _getRepositoriesFactory(mockHttpRequestFunction, mockTransformFunction); - const result = await getRepositories(context); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context); - expect(result).toEqual(transformResult); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await getRepositories(context, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - describe("getRepositoriesDefaultTransformFunction", () => { + describe("_getRepositoriesDefaultTransformFunction", () => { - it("should map correctly", async () => { + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } - const data: any = { - "_links": { - "self": { - "href": "/dms/r/" - } - }, - "repositories": [ - { - "_links": { - "HiItsMeLink1": { - "href": "HiItsMeHref1", - "templated": true - }, - "source": { - "href": "HiItsMeSource1", - } - }, - "id": "HiItsMeRepoId1", - "name": "HiItsMeName1", - "supportsFulltextSearch": true, - "serverId": "HiItsMeServerId1", - "available": true, - "isDefault": false, - "version": "HiItsMeVersion1" - }, { - "_links": { - "HiItsMeLink2": { - "href": "HiItsMeHref2", - "templated": true - }, - "source": { - "href": "HiItsMeSource2", - } - }, - "id": "HiItsMeRepoId2", - "name": "HiItsMeName2", - "supportsFulltextSearch": true, - "serverId": "HiItsMeServerId2", - "available": true, - "isDefault": false, - "version": "HiItsMeVersion2" - } - ], - "count": 1, - "hasAdminRight": false + it("should map repositories correctly", async () => { + const data = { + repositories: [ + { _links: { source: { href: "HiItsMeSource1" } }, id: "HiItsMeRepoId1", name: "HiItsMeName1" }, + { _links: { source: { href: "HiItsMeSource2" } }, id: "HiItsMeRepoId2", name: "HiItsMeName2" } + ] }; - mockHttpRequestFunction.mockResolvedValue({ data: data } as HttpResponse); - - const getRepositories = _getRepositoriesFactory(mockHttpRequestFunction, _getRepositoriesDefaultTransformFunction); - const result: Repository[] = await getRepositories(context); + const result: Repository[] = await onResponse(jsonResponse(data)); - data.repositories.forEach((repoDto: any, i: number) => { - expect(result[i]).toHaveProperty("repositoryId", repoDto.id); - expect(result[i]).toHaveProperty("name", repoDto.name); - expect(result[i]).toHaveProperty("sourceId", repoDto._links.source.href); - }); + expect(result).toEqual([ + { repositoryId: "HiItsMeRepoId1", name: "HiItsMeName1", sourceId: "/dms/r/HiItsMeRepoId1/source" }, + { repositoryId: "HiItsMeRepoId2", name: "HiItsMeName2", sourceId: "/dms/r/HiItsMeRepoId2/source" } + ]); }); }); }); diff --git a/packages/dms/src/repositories/get-repositories/get-repositories.ts b/packages/dms/src/repositories/get-repositories/get-repositories.ts index dd3b705f..e05262e1 100644 --- a/packages/dms/src/repositories/get-repositories/get-repositories.ts +++ b/packages/dms/src/repositories/get-repositories/get-repositories.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "../../index"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; import { Repository } from "../get-repository/get-repository"; /** @@ -7,34 +7,14 @@ import { Repository } from "../get-repository/get-repository"; * @internal * @category Repository */ -export function _getRepositoriesDefaultTransformFunction(response: HttpResponse, _: DvelopContext): Repository[] { - return response.data.repositories.map((repositoryDto: any) => { - return { - repositoryId: repositoryDto.id, - name: repositoryDto.name, - sourceId: repositoryDto._links["source"].href - }; - }); -} - -/** - * Factory for {@link getRepositories}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the getRepositories-function. A corresponding transformFunction has to be supplied. - * @internal - * @category Repository - */ -export function _getRepositoriesFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext) => T -): (context: DvelopContext) => Promise { - return async (context: DvelopContext) => { - const response: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: "/dms", - follows: ["allrepos"] - }); - return transformFunction(response, context); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + const data: any = await response.json(); + return data.repositories.map((repo: any) => ({ + repositoryId: repo.id, + name: repo.name, + sourceId: `/dms/r/${repo.id}/source` + })); } /** @@ -47,13 +27,17 @@ export function _getRepositoriesFactory( * systemBaseUri: "https://steamwheedle-cartel.d-velop.cloud", * authSessionId: "dQw4w9WgXcQ" * }); - * const repoList: string = repos.map(r => r.name).join(", "); - * console.log("Repositories:", repoList); // Booty Bay, Everlook, Gadgetzan, Ratchet * ``` * * @category Repository */ -/* istanbul ignore next */ -export async function getRepositories(context: DvelopContext): Promise { - return await _getRepositoriesFactory(_defaultHttpRequestFunction, _getRepositoriesDefaultTransformFunction)(context); -} \ No newline at end of file +export async function getRepositories(context: DvelopContext): Promise; +export async function getRepositories(context: DvelopContext, options: DvelopOptions): Promise; +export async function getRepositories( + context: DvelopContext, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, "/dms/r", { method: "GET" }, options); +} diff --git a/packages/dms/src/repositories/get-repository/get-repository.spec.ts b/packages/dms/src/repositories/get-repository/get-repository.spec.ts index 5b0f3ad9..e9e9921d 100644 --- a/packages/dms/src/repositories/get-repository/get-repository.spec.ts +++ b/packages/dms/src/repositories/get-repository/get-repository.spec.ts @@ -1,88 +1,67 @@ -import { DvelopContext } from "../../index"; -import { HttpResponse } from "../../utils/http"; -import { GetRepositoryParams, Repository, _getRepositoryDefaultTransformFunction, _getRepositoryFactory } from "./get-repository"; - +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { + GetRepositoryParams, + Repository, + onResponse, + getRepository, +} from "./get-repository"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); -describe("getRepositoryFactory", () => { +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("getRepository", () => { let context: DvelopContext; let params: GetRepositoryParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - - params = { - repositoryId: "HiItsMeRepositoryId" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; + params = { repositoryId: "HiItsMeRepositoryId" }; }); - it("should make correct request", async () => { - - const getRepository = _getRepositoryFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method GET", async () => { await getRepository(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/dms", - follows: ["repo"], - templates: { "repositoryid": params.repositoryId } - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + `/dms/r/${params.repositoryId}`, + { method: "GET" }, + expect.objectContaining({ onResponse: onResponse }) + ); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const getRepository = _getRepositoryFactory(mockHttpRequestFunction, mockTransformFunction); - await getRepository(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await getRepository(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - describe("getRepositoryDefaultTransformFunction", () => { + describe("_getRepositoryDefaultTransformFunction", () => { - it("should map correctly", async () => { + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } - const data: any = { - "_links": { - "HiItsMeLink": { - "href": "HiItsMeHref", - "templated": true - }, - "source": { - "href": "HiItsMeSource", - } - }, - "id": "HiItsMeRepoId", - "name": "HiItsMeName", - "supportsFulltextSearch": true, - "serverId": "HiItsMeServerId", - "available": true, - "isDefault": false, - "version": "HiItsMeVersion" + it("should map repository correctly", async () => { + const data = { + _links: { source: { href: "HiItsMeSource" } }, + id: "HiItsMeRepoId", + name: "HiItsMeName" }; - mockHttpRequestFunction.mockResolvedValue({ data: data } as HttpResponse); - - const getRepository = _getRepositoryFactory(mockHttpRequestFunction, _getRepositoryDefaultTransformFunction); - const result: Repository = await getRepository(context, params); + const result: Repository = await onResponse(jsonResponse(data)); - expect(result).toHaveProperty("repositoryId", data.id); - expect(result).toHaveProperty("name", data.name); - expect(result).toHaveProperty("sourceId", data._links.source.href); + expect(result).toEqual({ + repositoryId: "HiItsMeRepoId", + name: "HiItsMeName", + sourceId: "HiItsMeSource" + }); }); }); }); diff --git a/packages/dms/src/repositories/get-repository/get-repository.ts b/packages/dms/src/repositories/get-repository/get-repository.ts index 713888f5..d049e5f5 100644 --- a/packages/dms/src/repositories/get-repository/get-repository.ts +++ b/packages/dms/src/repositories/get-repository/get-repository.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "../../index"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/dms-error"; /** * Parameters for the {@link getRepository}-function. @@ -28,8 +28,9 @@ export interface Repository { * @internal * @category Repository */ -export function _getRepositoryDefaultTransformFunction(response: HttpResponse, _: DvelopContext, __: GetRepositoryParams): Repository { - const data: any = response.data; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + const data: any = await response.json(); return { repositoryId: data.id, name: data.name, @@ -37,27 +38,6 @@ export function _getRepositoryDefaultTransformFunction(response: HttpResponse, _ }; } -/** - * Factory for the {@link getRepository}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link getRepository}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category Repository - */ -export function _getRepositoryFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: GetRepositoryParams) => T, -): (context: DvelopContext, params: GetRepositoryParams) => Promise { - return async (context: DvelopContext, params: GetRepositoryParams) => { - const response: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: "/dms", - follows: ["repo"], - templates: { "repositoryid": params.repositoryId } - }); - return transformFunction(response, context, params); - }; -} - /** * Returns the {@link Repository}-object with the specified id. * @@ -71,12 +51,19 @@ export function _getRepositoryFactory( * repositoryId: "qnydFmqHuVo", * }); * - * console.log(repo.name); // Booty Bay Documents + * console.log(repo.name); * ``` * * @category Repository */ -/* istanbul ignore next */ -export async function getRepository(context: DvelopContext, params: GetRepositoryParams): Promise { - return _getRepositoryFactory(_defaultHttpRequestFunction, _getRepositoryDefaultTransformFunction)(context, params); +export async function getRepository(context: DvelopContext, params: GetRepositoryParams): Promise; +export async function getRepository(context: DvelopContext, params: GetRepositoryParams, options: DvelopOptions): Promise; +export async function getRepository( + context: DvelopContext, + params: GetRepositoryParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/dms/r/${params.repositoryId}`, { method: "GET" }, options); } diff --git a/packages/dms/src/utils/dms-error.spec.ts b/packages/dms/src/utils/dms-error.spec.ts new file mode 100644 index 00000000..4293aa43 --- /dev/null +++ b/packages/dms/src/utils/dms-error.spec.ts @@ -0,0 +1,134 @@ +import { BadInputError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; +import { DmsError, ensureSuccessResponse } from "./dms-error"; + +describe("ensureSuccessResponse", () => { + + it("should not throw on 200", async () => { + const response = new Response("anything", { status: 200 }); + await expect(ensureSuccessResponse(response)).resolves.toBeUndefined(); + }); + + it("should not throw on 204", async () => { + const response = new Response(null, { status: 204 }); + await expect(ensureSuccessResponse(response)).resolves.toBeUndefined(); + }); + + describe("on statusCode 400", () => { + + it("should throw BadInputError with reason from body", async () => { + const response = new Response(JSON.stringify({ reason: "HiItsMeErrorReason" }), { + status: 400, + headers: { "Content-Type": "application/json" } + }); + + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(BadInputError); + expect(err.message).toEqual("HiItsMeErrorReason"); + }); + + it("should throw generic BadInputError on missing body", async () => { + const response = new Response(null, { status: 400 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(BadInputError); + expect(err.message).toEqual("DMS-App responded with Status 400 indicating bad Request-Parameters."); + }); + }); + + describe("on statusCode 401", () => { + + it("should throw UnauthorizedError with reason from body", async () => { + const response = new Response(JSON.stringify({ reason: "HiItsMeErrorReason" }), { + status: 401, + headers: { "Content-Type": "application/json" } + }); + + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(UnauthorizedError); + expect(err.message).toEqual("HiItsMeErrorReason"); + }); + + it("should throw UnauthorizedError with string body", async () => { + const response = new Response("HiItsMeErrorReason", { status: 401 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(UnauthorizedError); + expect(err.message).toEqual("HiItsMeErrorReason"); + }); + + it("should throw generic UnauthorizedError on missing body", async () => { + const response = new Response(null, { status: 401 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(UnauthorizedError); + expect(err.message).toEqual("DMS-App responded with Status 401 indicating bad authSessionId."); + }); + }); + + describe("on statusCode 403", () => { + + it("should throw ForbiddenError with reason from body", async () => { + const response = new Response(JSON.stringify({ reason: "HiItsMeErrorReason" }), { + status: 403, + headers: { "Content-Type": "application/json" } + }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(ForbiddenError); + expect(err.message).toEqual("HiItsMeErrorReason"); + }); + + it("should throw generic ForbiddenError on missing body", async () => { + const response = new Response(null, { status: 403 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(ForbiddenError); + expect(err.message).toEqual("DMS-App responded with Status 403 indicating a forbidden action."); + }); + }); + + describe("on statusCode 404", () => { + + it("should throw NotFoundError with reason from body", async () => { + const response = new Response(JSON.stringify({ reason: "HiItsMeErrorReason" }), { + status: 404, + headers: { "Content-Type": "application/json" } + }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(NotFoundError); + expect(err.message).toEqual("HiItsMeErrorReason"); + }); + + it("should throw NotFoundError with LocalizedMessage from body", async () => { + const response = new Response(JSON.stringify({ LocalizedMessage: "HiItsMeErrorReason" }), { + status: 404, + headers: { "Content-Type": "application/json" } + }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(NotFoundError); + expect(err.message).toEqual("HiItsMeErrorReason"); + }); + + it("should throw generic NotFoundError on missing body", async () => { + const response = new Response(null, { status: 404 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(NotFoundError); + expect(err.message).toEqual("DMS-App responded with Status 404 indicating a requested resource does not exist."); + }); + }); + + describe("on unknown statusCode", () => { + + it("should throw DmsError with reason from body", async () => { + const response = new Response(JSON.stringify({ reason: "HiItsMeErrorReason" }), { + status: 500, + headers: { "Content-Type": "application/json" } + }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(DmsError); + expect(err.message).toEqual("HiItsMeErrorReason"); + }); + + it("should throw generic DmsError on missing body", async () => { + const response = new Response(null, { status: 500 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(DmsError); + expect(err.message).toEqual("DMS-App responded with status 500."); + }); + }); +}); diff --git a/packages/dms/src/utils/dms-error.ts b/packages/dms/src/utils/dms-error.ts new file mode 100644 index 00000000..3f0d318a --- /dev/null +++ b/packages/dms/src/utils/dms-error.ts @@ -0,0 +1,58 @@ +import { BadInputError, DvelopSdkError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; + +/** + * Generic Error for dms-package. + * @category Error + */ +/* istanbul ignore next */ +export class DmsError extends DvelopSdkError { + + constructor(public message: string, public originalError?: Error) { + super(message); + Object.setPrototypeOf(this, DmsError.prototype); + } +} + +/** + * Throws a typed error if the response indicates failure. + * @internal + * @category Http + */ +export async function ensureSuccessResponse(response: Response): Promise { + + if (response.ok) return; + + let body: any; + try { + body = await response.clone().json(); + } catch { + try { + body = await response.clone().text(); + } catch { + body = undefined; + } + } + + let reason: string | undefined; + if (body) { + if (typeof body === "string") { + reason = body; + } else { + reason = body.reason ?? body.LocalizedMessage ?? undefined; + reason += body.details ? ` Details: ${body.details}` : ""; + } + } + + switch (response.status) { + case 400: + throw new BadInputError(reason ?? "DMS-App responded with Status 400 indicating bad Request-Parameters."); + case 401: + throw new UnauthorizedError(reason ?? "DMS-App responded with Status 401 indicating bad authSessionId."); + case 403: + throw new ForbiddenError(reason ?? "DMS-App responded with Status 403 indicating a forbidden action."); + case 404: + throw new NotFoundError(reason ?? "DMS-App responded with Status 404 indicating a requested resource does not exist."); + default: + throw new DmsError(reason ?? `DMS-App responded with status ${response.status}.`); + } +} diff --git a/packages/dms/src/utils/http.spec.ts b/packages/dms/src/utils/http.spec.ts deleted file mode 100644 index 47c6283b..00000000 --- a/packages/dms/src/utils/http.spec.ts +++ /dev/null @@ -1,392 +0,0 @@ -import { BadInputError, DvelopContext, DvelopHttpClient, DvelopHttpError, DvelopHttpRequestConfig, DvelopHttpResponse, DvelopSdkError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; -import { _defaultHttpRequestFunctionFactory, DmsError } from "./http"; - -describe("defaultHttpRequestFunctionFactory", () => { - - let mockRequestFunction = jest.fn(); - let mockHttpClient: DvelopHttpClient = { - request: mockRequestFunction - }; - - let context: DvelopContext; - let config: DvelopHttpRequestConfig; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it("should call request", async () => { - - const response: DvelopHttpResponse = { - data: "HiItsMeResponse" - } as DvelopHttpResponse; - mockRequestFunction.mockResolvedValue(response); - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - const result = await requestFunction(context, config); - - expect(mockRequestFunction).toHaveBeenCalledTimes(1); - expect(result).toBe(response); - }); - - describe("error-handling", () => { - - describe("on statusCode 400", () => { - - it("should throw BadInputError on DmsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 400, - data: { - reason: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof BadInputError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).reason); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic BadInputError on missing DmsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 400 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof BadInputError).toBeTruthy(); - expect(expectedError.message).toEqual("DMS-App responded with Status 400 indicating bad Request-Parameters. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on statusCode 401", () => { - - it("should throw Unauthorized on DmsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 401, - data: { - reason: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof UnauthorizedError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).reason); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw Unauthorized on body", async () => { - - const error: DvelopHttpError = { - response: { - status: 401, - data: "HiItsMeErrorReason" - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: UnauthorizedError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof DvelopSdkError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any)); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic DMSError on missing Body", async () => { - - const error: DvelopHttpError = { - response: { - status: 401 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof UnauthorizedError).toBeTruthy(); - expect(expectedError.message).toEqual("DMS-App responded with Status 401 indicating bad authSessionId."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on statusCode 403", () => { - - it("should throw ForbiddenError on DmsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 403, - data: { - reason: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof ForbiddenError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).reason); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic ForbiddenError on missing DmsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 403 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof ForbiddenError).toBeTruthy(); - expect(expectedError.message).toEqual("DMS-App responded with Status 403 indicating a forbidden action. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on statusCode 404", () => { - - it("should throw NotFoundError on DmsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 404, - data: { - reason: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotFoundError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).reason); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw NotFoundError on LocalizedMessage", async () => { - - const error: DvelopHttpError = { - response: { - status: 404, - data: { - LocalizedMessage: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotFoundError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).LocalizedMessage); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic BadInputError on missing DmsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 404 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotFoundError).toBeTruthy(); - expect(expectedError.message).toEqual("DMS-App responded with Status 404 indicating a requested resource does not exist. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on unknown statusCode", () => { - - it("should throw DMSError on DmsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 500, - data: { - reason: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof DmsError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).reason); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic DMSError on missing DmsErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 500 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof DmsError).toBeTruthy(); - expect(expectedError.message).toEqual(`DMS-App responded with status ${error.response.status}. See 'originalError'-property for details.`); - expect(expectedError.originalError).toBe(error); - }); - }); - - - - - it("should throw generic DMSError on no response", async () => { - const error = new Error("HiItsMeError"); - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof DmsError).toBeTruthy(); - expect(expectedError.message).toEqual(`Request to DMS-App failed: ${error.message}. See 'originalError'-property for details.`); - expect(expectedError.originalError).toBe(error); - }); - - - }); - -}); \ No newline at end of file diff --git a/packages/dms/src/utils/http.ts b/packages/dms/src/utils/http.ts deleted file mode 100644 index 70a75f2a..00000000 --- a/packages/dms/src/utils/http.ts +++ /dev/null @@ -1,63 +0,0 @@ - -import { DvelopContext, DvelopHttpRequestConfig, DvelopHttpResponse, DvelopHttpClient, defaultDvelopHttpClientFactory, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError, DvelopSdkError } from "@dvelop-sdk/core"; -export { DvelopHttpRequestConfig as HttpConfig, DvelopHttpResponse as HttpResponse } from "@dvelop-sdk/core"; - -/** -* Generic Error for dms-package. -* @category Error -*/ -/* istanbul ignore next */ -export class DmsError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars - constructor(public message: string, public originalError?: Error) { - super(message); - Object.setPrototypeOf(this, DmsError.prototype); - } -} - -/** - * Factory used to create the default httpRequestFunction. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @internal - * @category Http - */ -export function _defaultHttpRequestFunctionFactory(httpClient: DvelopHttpClient): (context: DvelopContext, config: DvelopHttpRequestConfig) => Promise { - return async (context: DvelopContext, config: DvelopHttpRequestConfig) => { - - try { - return await httpClient.request(context, config); - } catch (error: any) { - - if (error.response) { - - switch (error.response.status) { - case 400: - throw new BadInputError(error.response.data?.reason || "DMS-App responded with Status 400 indicating bad Request-Parameters. See 'originalError'-property for details.", error); - - case 401: - throw new UnauthorizedError(error.response.data?.reason || error.response.data || "DMS-App responded with Status 401 indicating bad authSessionId.", error); - - case 403: - throw new ForbiddenError(error.response.data?.reason || "DMS-App responded with Status 403 indicating a forbidden action. See 'originalError'-property for details.", error); - - case 404: - throw new NotFoundError(error.response.data?.reason || error.response.data?.LocalizedMessage || "DMS-App responded with Status 404 indicating a requested resource does not exist. See 'originalError'-property for details.", error); - - default: - throw new DmsError(error.response.data?.reason || `DMS-App responded with status ${error.response.status}. See 'originalError'-property for details.`, error); - } - } else { - throw new DmsError(`Request to DMS-App failed: ${error.message}. See 'originalError'-property for details.`, error); - } - } - }; -} - -/** - * Default httpRequestFunction used in dms-package. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @internal - * @category Http - */ -/* istanbul ignore next */ -export async function _defaultHttpRequestFunction(context: DvelopContext, config: DvelopHttpRequestConfig): Promise { - return _defaultHttpRequestFunctionFactory(defaultDvelopHttpClientFactory())(context, config); -} \ No newline at end of file diff --git a/packages/dms/tsconfig.build.json b/packages/dms/tsconfig.build.json new file mode 100644 index 00000000..0c9b67d5 --- /dev/null +++ b/packages/dms/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig-base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.spec.ts", "src/**/tmp-*.ts"] +} diff --git a/packages/dms/tsconfig.json b/packages/dms/tsconfig.json index 1e5dc209..3912f472 100644 --- a/packages/dms/tsconfig.json +++ b/packages/dms/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "outDir": "lib", /* Redirect output structure to the directory. */ - "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "noEmit": true, + "composite": false, + "declaration": false, + "declarationMap": false }, - "include": ["src/**/*"], - "exclude": ["src/**/*.spec.ts"] -} \ No newline at end of file + "include": ["src/**/*"] +} diff --git a/packages/express-utils/package.json b/packages/express-utils/package.json index 94dc4eec..fbf4ea14 100644 --- a/packages/express-utils/package.json +++ b/packages/express-utils/package.json @@ -1,7 +1,7 @@ { "name": "@dvelop-sdk/express-utils", "description": "This package contains middleware-functions for the express-framework and d.velop app-building.", - "version": "1.2.5", + "version": "1.3.0", "license": "Apache-2.0", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,9 +20,9 @@ "access": "public" }, "dependencies": { - "@dvelop-sdk/app-router": "^3.2.2", - "@dvelop-sdk/core": "^2.2.3", - "@dvelop-sdk/identityprovider": "^4.0.8", + "@dvelop-sdk/app-router": "^3.3.0", + "@dvelop-sdk/core": "^3.0.0", + "@dvelop-sdk/identityprovider": "^5.0.0", "@types/express": "^4.17.13" } } diff --git a/packages/express-utils/src/middleware/dvelop-context-middleware/dvelop-context-middleware.spec.ts b/packages/express-utils/src/middleware/dvelop-context-middleware/dvelop-context-middleware.spec.ts index 8041f11f..fa75a296 100644 --- a/packages/express-utils/src/middleware/dvelop-context-middleware/dvelop-context-middleware.spec.ts +++ b/packages/express-utils/src/middleware/dvelop-context-middleware/dvelop-context-middleware.spec.ts @@ -1,12 +1,7 @@ import { NextFunction, Request, Response } from "express"; import { - DVELOP_REQUEST_ID_HEADER, - DVELOP_REQUEST_SIGNATURE_HEADER, - DVELOP_SYSTEM_BASE_URI_HEADER, - DVELOP_TENANT_ID_HEADER, TraceContext, TraceContextError, - TRACEPARENT_HEADER, } from "@dvelop-sdk/core"; import { contextMiddlewareFactory } from "./dvelop-context-middleware"; import "../../index"; @@ -40,19 +35,19 @@ describe("contextMiddlewareFactory", () => { (mockReq.header as jest.Mock).mockImplementation((header: string) => { switch (header) { - case DVELOP_SYSTEM_BASE_URI_HEADER: + case "x-dv-baseuri": return systemBaseUri; - case DVELOP_TENANT_ID_HEADER: + case "x-dv-tenant-id": return tenantId; - case DVELOP_REQUEST_ID_HEADER: + case "x-dv-request-id": return requestId; - case DVELOP_REQUEST_SIGNATURE_HEADER: + case "x-dv-sig-1": return requestSignature; - case TRACEPARENT_HEADER: + case "traceparent": return undefined; default: @@ -80,10 +75,10 @@ describe("contextMiddlewareFactory", () => { (mockReq.header as jest.Mock).mockImplementation((header: string) => { switch (header) { - case DVELOP_REQUEST_ID_HEADER: + case "x-dv-request-id": return requestId; - case TRACEPARENT_HEADER: + case "traceparent": return undefined; default: @@ -111,13 +106,13 @@ describe("contextMiddlewareFactory", () => { (mockReq.header as jest.Mock).mockImplementation((header: string) => { switch (header) { - case DVELOP_TENANT_ID_HEADER: + case "x-dv-tenant-id": return tenantId; - case DVELOP_REQUEST_ID_HEADER: + case "x-dv-request-id": return requestId; - case TRACEPARENT_HEADER: + case "traceparent": return undefined; default: @@ -145,7 +140,7 @@ describe("contextMiddlewareFactory", () => { (mockReq.header as jest.Mock).mockImplementation((header: string) => { switch (header) { - case TRACEPARENT_HEADER: + case "traceparent": return traceparentHeader; default: return "HiItsMeHeader"; @@ -166,7 +161,7 @@ describe("contextMiddlewareFactory", () => { (mockReq.header as jest.Mock).mockImplementation((header: string) => { switch (header) { - case TRACEPARENT_HEADER: + case "traceparent": return undefined; default: return "HiItsMeHeader"; @@ -190,7 +185,7 @@ describe("contextMiddlewareFactory", () => { (mockReq.header as jest.Mock).mockImplementation((header: string) => { switch (header) { - case TRACEPARENT_HEADER: + case "traceparent": return traceparentHeader; default: return "HiItsMeHeader"; diff --git a/packages/express-utils/src/middleware/dvelop-context-middleware/dvelop-context-middleware.ts b/packages/express-utils/src/middleware/dvelop-context-middleware/dvelop-context-middleware.ts index 0038cbe8..982b7bf1 100644 --- a/packages/express-utils/src/middleware/dvelop-context-middleware/dvelop-context-middleware.ts +++ b/packages/express-utils/src/middleware/dvelop-context-middleware/dvelop-context-middleware.ts @@ -1,9 +1,4 @@ import { - DVELOP_REQUEST_ID_HEADER, - DVELOP_REQUEST_SIGNATURE_HEADER, - DVELOP_SYSTEM_BASE_URI_HEADER, - DVELOP_TENANT_ID_HEADER, - TRACEPARENT_HEADER, TraceContext, parseTraceparentHeader, generateTraceContext @@ -28,19 +23,19 @@ export function contextMiddlewareFactory( if (systemBaseUri) { req.dvelopContext = { systemBaseUri: systemBaseUri, - tenantId: tenantId || req.header(DVELOP_TENANT_ID_HEADER), - requestId: req.header(DVELOP_REQUEST_ID_HEADER) + tenantId: tenantId || req.header("x-dv-tenant-id"), + requestId: req.header("x-dv-request-id") } } else { req.dvelopContext = { - systemBaseUri: req.header(DVELOP_SYSTEM_BASE_URI_HEADER), - tenantId: req.header(DVELOP_TENANT_ID_HEADER), - requestId: req.header(DVELOP_REQUEST_ID_HEADER), - requestSignature: req.header(DVELOP_REQUEST_SIGNATURE_HEADER), + systemBaseUri: req.header("x-dv-baseuri"), + tenantId: req.header("x-dv-tenant-id"), + requestId: req.header("x-dv-request-id"), + requestSignature: req.header("x-dv-sig-1"), }; } - const traceparentHeader = req.header(TRACEPARENT_HEADER); + const traceparentHeader = req.header("traceparent"); if (traceparentHeader) { try { diff --git a/packages/express-utils/tsconfig.build.json b/packages/express-utils/tsconfig.build.json new file mode 100644 index 00000000..0c9b67d5 --- /dev/null +++ b/packages/express-utils/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig-base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.spec.ts", "src/**/tmp-*.ts"] +} diff --git a/packages/express-utils/tsconfig.json b/packages/express-utils/tsconfig.json index 5ad7fa35..3912f472 100644 --- a/packages/express-utils/tsconfig.json +++ b/packages/express-utils/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "outDir": "lib", /* Redirect output structure to the directory. */ - "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "noEmit": true, + "composite": false, + "declaration": false, + "declarationMap": false }, - "include": ["src/**/*"], - "exclude": ["src/**/*.spec.ts"], -} \ No newline at end of file + "include": ["src/**/*"] +} diff --git a/packages/identityprovider/package.json b/packages/identityprovider/package.json index 51333e4e..5a68ee7a 100644 --- a/packages/identityprovider/package.json +++ b/packages/identityprovider/package.json @@ -1,7 +1,7 @@ { "name": "@dvelop-sdk/identityprovider", "description": "This package contains functionality for the Identityprovider-App in the d.velop cloud.", - "version": "4.0.11", + "version": "5.0.0", "license": "Apache-2.0", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -23,6 +23,6 @@ "license": "license-checker --production --onlyAllow Apache-2.0;MIT;ISC;BSD-2-Clause;BSD-3-Clause" }, "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } } diff --git a/packages/identityprovider/src/authentication/get-auth-session/get-auth-session.spec.ts b/packages/identityprovider/src/authentication/get-auth-session/get-auth-session.spec.ts index 10d2322c..30b20917 100644 --- a/packages/identityprovider/src/authentication/get-auth-session/get-auth-session.spec.ts +++ b/packages/identityprovider/src/authentication/get-auth-session/get-auth-session.spec.ts @@ -1,66 +1,51 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpResponse } from "../../utils/http"; -import { AuthSession, _getAuthSessionDefaultTransformFunction, _getAuthSessionFactory } from "./get-auth-session"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { AuthSession, onResponse, getAuthSession } from "./get-auth-session"; -describe("getAuthSessionFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("getAuthSession", () => { let context: DvelopContext; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; }); - it("should make correct request", async () => { - - const getAuthSession = _getAuthSessionFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method GET", async () => { await getAuthSession(context); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/identityprovider", - follows: ["login"] - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith(context, "/identityprovider/login", { method: "GET" }, expect.objectContaining({ onResponse: onResponse })); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const getAuthSession = _getAuthSessionFactory(mockHttpRequestFunction, mockTransformFunction); - await getAuthSession(context); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await getAuthSession(context, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - describe("getAuthSessionDefaultTransformFunction", () => { + describe("onResponse", () => { - it("should map correctly", async () => { + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } - const data: any = { + it("should map and transform the auth session", async () => { + const data = { AuthSessionId: "HiItsMeAuthSessionId", - Expire: "1992-02-16T16:11:03.8019256Z" + Expire: "1992-02-16T16:11:03.000Z" }; - mockHttpRequestFunction.mockResolvedValue({ data: data } as HttpResponse); - - const getAuthSession = _getAuthSessionFactory(mockHttpRequestFunction, _getAuthSessionDefaultTransformFunction); - const result: AuthSession = await getAuthSession(context); + const result: AuthSession = await onResponse(jsonResponse(data)); - expect(result).toHaveProperty("id", data.AuthSessionId); - expect(result).toHaveProperty("expire", new Date(data.Expire)); + expect(result.id).toBe("HiItsMeAuthSessionId"); + expect(result.expire).toEqual(new Date("1992-02-16T16:11:03.000Z")); }); }); }); diff --git a/packages/identityprovider/src/authentication/get-auth-session/get-auth-session.ts b/packages/identityprovider/src/authentication/get-auth-session/get-auth-session.ts index da0e7407..b6b49b71 100644 --- a/packages/identityprovider/src/authentication/get-auth-session/get-auth-session.ts +++ b/packages/identityprovider/src/authentication/get-auth-session/get-auth-session.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "../../../../core/lib"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/identityprovider-error"; /** * Used for authentication in the d.velop cloud. Refer to the [documentation](https://developer.d-velop.de/documentation/idpapi/en/identityprovider-app-201523580.html#validating-the-login) for further information. @@ -13,34 +13,16 @@ export interface AuthSession { } /** - * Default transform-function provided to the {@link getAuthSession}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * Default `onResponse` provided to the {@link getAuthSession}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Authentication */ -export function _getAuthSessionDefaultTransformFunction(response: HttpResponse, _: DvelopContext): AuthSession { +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + const data: any = await response.json(); return { - id: response.data.AuthSessionId, - expire: new Date(response.data.Expire) - }; -} - -/** - * Factory for the {@link getAuthSession}-function. See internals for more information. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link getAuthSession}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category Authentication - */ -export function _getAuthSessionFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext) => T, -): (context: DvelopContext) => Promise { - return async (context: DvelopContext) => { - const response: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: "/identityprovider", - follows: ["login"], - }); - return transformFunction(response, context); + id: data.AuthSessionId, + expire: new Date(data.Expire) }; } @@ -57,12 +39,18 @@ export function _getAuthSessionFactory( * authSessionId: "dQw4w9WgXcQ" * }); * - console.log(authSession); + * console.log(authSession); * ``` * * @category Authentication */ -/* istanbul ignore next */ -export async function getAuthSession(context: DvelopContext): Promise { - return _getAuthSessionFactory(_defaultHttpRequestFunction, _getAuthSessionDefaultTransformFunction)(context); -} \ No newline at end of file +export async function getAuthSession(context: DvelopContext): Promise; +export async function getAuthSession(context: DvelopContext, options: DvelopOptions): Promise; +export async function getAuthSession( + context: DvelopContext, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, "/identityprovider/login", { method: "GET" }, options); +} diff --git a/packages/identityprovider/src/authentication/get-impersonated-auth-session-id/get-impersonated-auth-session-id.spec.ts b/packages/identityprovider/src/authentication/get-impersonated-auth-session-id/get-impersonated-auth-session-id.spec.ts index e3ea559f..1dbbe3c8 100644 --- a/packages/identityprovider/src/authentication/get-impersonated-auth-session-id/get-impersonated-auth-session-id.spec.ts +++ b/packages/identityprovider/src/authentication/get-impersonated-auth-session-id/get-impersonated-auth-session-id.spec.ts @@ -1,71 +1,51 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpResponse } from "../../utils/http"; -import { GetImpersonatedAuthSessionIdParams, _getImpersonatedAuthSessionIdDefaultTransformFunction, _getImpersonatedAuthSessionIdFactory } from "./get-impersonated-auth-session-id"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { GetImpersonatedAuthSessionIdParams, onResponse, getImpersonatedAuthSessionId } from "./get-impersonated-auth-session-id"; -describe("getImpersonatedAuthSessionIdFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; + +describe("getImpersonatedAuthSessionId", () => { let context: DvelopContext; let params: GetImpersonatedAuthSessionIdParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - - params = { - userId: "HiItsMeUserId" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; + params = { userId: "HiItsMeUserId" }; }); - it("should make correct request", async () => { - - const getImpersonatedAuthSessionId = _getImpersonatedAuthSessionIdFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method GET and encoded userId", async () => { await getImpersonatedAuthSessionId(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/identityprovider/impersonatesession", - params: { - userId: params.userId - } - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith(context, "/identityprovider/impersonatesession?userId=HiItsMeUserId", { method: "GET" }, expect.objectContaining({ onResponse: onResponse })); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const getImpersonatedAuthSessionId = _getImpersonatedAuthSessionIdFactory(mockHttpRequestFunction, mockTransformFunction); - await getImpersonatedAuthSessionId(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should encode special characters in the userId", async () => { + await getImpersonatedAuthSessionId(context, { userId: "user/with spaces" }); + expect(mockDvelopFetch.mock.calls[0][1]).toBe("/identityprovider/impersonatesession?userId=user%2Fwith%20spaces"); }); - describe("getImpersonatedAuthSessionIdDefaultTransformFunction", () => { - - it("should map correctly", async () => { - - const data: any = { - authSessionId: "HiItsMeAuthSessionId" - }; + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await getImpersonatedAuthSessionId(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - mockHttpRequestFunction.mockResolvedValue({ data: data } as HttpResponse); + describe("onResponse", () => { - const getImpersonatedAuthSessionId = _getImpersonatedAuthSessionIdFactory(mockHttpRequestFunction, _getImpersonatedAuthSessionIdDefaultTransformFunction); - const result: string = await getImpersonatedAuthSessionId(context, params); + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } - expect(result).toEqual(data.authSessionId); + it("should return the authSessionId from the response body", async () => { + const result: string = await onResponse(jsonResponse({ authSessionId: "HiItsMeAuthSessionId" })); + expect(result).toEqual("HiItsMeAuthSessionId"); }); }); -}); \ No newline at end of file +}); diff --git a/packages/identityprovider/src/authentication/get-impersonated-auth-session-id/get-impersonated-auth-session-id.ts b/packages/identityprovider/src/authentication/get-impersonated-auth-session-id/get-impersonated-auth-session-id.ts index f4b1bb31..b8dad8f2 100644 --- a/packages/identityprovider/src/authentication/get-impersonated-auth-session-id/get-impersonated-auth-session-id.ts +++ b/packages/identityprovider/src/authentication/get-impersonated-auth-session-id/get-impersonated-auth-session-id.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/identityprovider-error"; /** * Parameters for the {@link getImpersonatedAuthSessionId}-function. @@ -10,34 +10,14 @@ export interface GetImpersonatedAuthSessionIdParams { } /** - * Default transform-function provided to the {@link getImpersontedAuthSessionId}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * Default `onResponse` provided to the {@link getImpersonatedAuthSessionId}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Authentication */ -export function _getImpersonatedAuthSessionIdDefaultTransformFunction(response: HttpResponse, _: DvelopContext, __: GetImpersonatedAuthSessionIdParams): string { - return response.data.authSessionId; -} - -/** - * Factory for the {@link getImpersonatedAuthSessionId}}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link getImpersonatedAuthSessionId}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category Authentication - */ -export function _getImpersonatedAuthSessionIdFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: GetImpersonatedAuthSessionIdParams) => T, -): (context: DvelopContext, params: GetImpersonatedAuthSessionIdParams) => Promise { - return async (context: DvelopContext, params: GetImpersonatedAuthSessionIdParams) => { - const response: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: "/identityprovider/impersonatesession", - params: { - userId: params.userId - } - }); - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + const data: any = await response.json(); + return data.authSessionId; } /** @@ -58,7 +38,14 @@ export function _getImpersonatedAuthSessionIdFactory( * ``` * @category Authentication */ -/* istanbul ignore next */ -export async function getImpersonatedAuthSessionId(context: DvelopContext, params: GetImpersonatedAuthSessionIdParams): Promise { - return _getImpersonatedAuthSessionIdFactory(_defaultHttpRequestFunction, _getImpersonatedAuthSessionIdDefaultTransformFunction)(context, params); -} \ No newline at end of file +export async function getImpersonatedAuthSessionId(context: DvelopContext, params: GetImpersonatedAuthSessionIdParams): Promise; +export async function getImpersonatedAuthSessionId(context: DvelopContext, params: GetImpersonatedAuthSessionIdParams, options: DvelopOptions): Promise; +export async function getImpersonatedAuthSessionId( + context: DvelopContext, + params: GetImpersonatedAuthSessionIdParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/identityprovider/impersonatesession?userId=${encodeURIComponent(params.userId)}`, { method: "GET" }, options); +} diff --git a/packages/identityprovider/src/authentication/request-app-session/request-app-session.spec.ts b/packages/identityprovider/src/authentication/request-app-session/request-app-session.spec.ts index 35400c39..2c4cd86d 100644 --- a/packages/identityprovider/src/authentication/request-app-session/request-app-session.spec.ts +++ b/packages/identityprovider/src/authentication/request-app-session/request-app-session.spec.ts @@ -1,58 +1,49 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpResponse } from "../../utils/http"; -import { RequestAppSessionParams, _requestAppSessionFactory } from "./request-app-session"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { RequestAppSessionParams, onResponse, requestAppSession } from "./request-app-session"; -describe("requestAppSessionFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; + +describe("requestAppSession", () => { let context: DvelopContext; let params: RequestAppSessionParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri", - requestId: "HiItsMeRequestId" - }; - - params = { - appName: "HiItsMeAppName", - callback: "HiItsMeCallBack" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri", requestId: "HiItsMeRequestId" }; + params = { appName: "HiItsMeAppName", callback: "HiItsMeCallBack" }; }); - it("should make correct request", async () => { - - const requestAppSession = _requestAppSessionFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method POST and the correct body", async () => { await requestAppSession(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith(context, "/identityprovider/appsession", { method: "POST", - url: "/identityprovider/appsession", - data: { + body: JSON.stringify({ appname: params.appName, callback: params.callback, requestid: context.requestId - } - }); + }) + }, expect.objectContaining({ onResponse: onResponse })); }); - it("should pass response to transform and return transform-result", async () => { + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await requestAppSession(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); + describe("onResponse", () => { - const requestAppSession = _requestAppSessionFactory(mockHttpRequestFunction, mockTransformFunction); - await requestAppSession(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should resolve without a value on success", async () => { + const response = new Response(null, { status: 200 }); + await expect(onResponse(response)).resolves.toBeUndefined(); + }); }); -}); \ No newline at end of file +}); diff --git a/packages/identityprovider/src/authentication/request-app-session/request-app-session.ts b/packages/identityprovider/src/authentication/request-app-session/request-app-session.ts index 37acd045..74bae998 100644 --- a/packages/identityprovider/src/authentication/request-app-session/request-app-session.ts +++ b/packages/identityprovider/src/authentication/request-app-session/request-app-session.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "../../../../core/lib"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/identityprovider-error"; /** * Parameters for the {@link requestAppSession}-function. @@ -13,29 +13,12 @@ export interface RequestAppSessionParams { } /** - * Factory for the {@link requestAppSession}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link requestAppSession}-function. A corresponding transformFunction has to be supplied. + * Default `onResponse` provided to the {@link requestAppSession}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * @internal * @category Authentication */ -export function _requestAppSessionFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: RequestAppSessionParams) => T, -): (context: DvelopContext, params: RequestAppSessionParams) => Promise { - - return async (context: DvelopContext, params: RequestAppSessionParams) => { - - const response = await httpRequestFunction(context, { - method: "POST", - url: "/identityprovider/appsession", - data: { - appname: params.appName, - callback: params.callback, - requestid: context.requestId - } - }); - - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } /** @@ -55,7 +38,21 @@ export function _requestAppSessionFactory( * ``` * @category Authentication */ -/* istanbul ignore next */ -export async function requestAppSession(context: DvelopContext, params: RequestAppSessionParams): Promise { - return await _requestAppSessionFactory(_defaultHttpRequestFunction, () => { })(context, params); -} \ No newline at end of file +export async function requestAppSession(context: DvelopContext, params: RequestAppSessionParams): Promise; +export async function requestAppSession(context: DvelopContext, params: RequestAppSessionParams, options: DvelopOptions): Promise; +export async function requestAppSession( + context: DvelopContext, + params: RequestAppSessionParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, "/identityprovider/appsession", { + method: "POST", + body: JSON.stringify({ + appname: params.appName, + callback: params.callback, + requestid: context.requestId + }) + }, options); +} diff --git a/packages/identityprovider/src/authentication/validate-auth-session-id/validate-auth-session-id.spec.ts b/packages/identityprovider/src/authentication/validate-auth-session-id/validate-auth-session-id.spec.ts index 3b7b9f4a..aba413f1 100644 --- a/packages/identityprovider/src/authentication/validate-auth-session-id/validate-auth-session-id.spec.ts +++ b/packages/identityprovider/src/authentication/validate-auth-session-id/validate-auth-session-id.spec.ts @@ -1,68 +1,53 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpResponse } from "../../utils/http"; -import { DvelopUser, _validateAuthSessionIdDefaultTransformFunction, _validateAuthSessionIdFactory } from "./validate-auth-session-id"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { DvelopUser, onResponse, validateAuthSessionId } from "./validate-auth-session-id"; -describe("validateAuthSessionIdFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; + +describe("validateAuthSessionId", () => { let context: DvelopContext; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; }); - it("should make correct request", async () => { - - const validateAuthSessionId = _validateAuthSessionIdFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method GET", async () => { await validateAuthSessionId(context); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/identityprovider", - follows: ["validate"] - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith(context, "/identityprovider/validate", { method: "GET" }, expect.objectContaining({ onResponse: onResponse })); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const validateAuthSessionId = _validateAuthSessionIdFactory(mockHttpRequestFunction, mockTransformFunction); - await validateAuthSessionId(context); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await validateAuthSessionId(context, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - describe("validateAuthSessionIdDefaultTransformFunction", () => { + describe("onResponse", () => { - it("should map correctly", async () => { + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } - const data: any = { + it("should return the user from the response body", async () => { + const data = { name: { familyName: "HiItsMeFamilyName", givenName: "HiItsMeGivenName" } }; - mockHttpRequestFunction.mockResolvedValue({ data: data } as HttpResponse); - - const validateAuthSessionId = _validateAuthSessionIdFactory(mockHttpRequestFunction, _validateAuthSessionIdDefaultTransformFunction); - const result: DvelopUser = await validateAuthSessionId(context); + const result: DvelopUser = await onResponse(jsonResponse(data)); expect(result.name.familyName).toEqual(data.name.familyName); expect(result.name.givenName).toEqual(data.name.givenName); }); }); -}); \ No newline at end of file +}); diff --git a/packages/identityprovider/src/authentication/validate-auth-session-id/validate-auth-session-id.ts b/packages/identityprovider/src/authentication/validate-auth-session-id/validate-auth-session-id.ts index 3ec330ae..3e51bc92 100644 --- a/packages/identityprovider/src/authentication/validate-auth-session-id/validate-auth-session-id.ts +++ b/packages/identityprovider/src/authentication/validate-auth-session-id/validate-auth-session-id.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "../../../../core/lib"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/identityprovider-error"; /** * User representation according to the [System for Cross-domain Identity Management (SCIM)]{@link https://tools.ietf.org/html/rfc7644}. @@ -41,31 +41,13 @@ export interface DvelopUser { } /** - * Default transform-function provided to the {@link validateAuthSessionId}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * Default `onResponse` provided to the {@link validateAuthSessionId}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Authentication */ -export function _validateAuthSessionIdDefaultTransformFunction(response: HttpResponse, _: DvelopContext): DvelopUser { - return response.data; -} - -/** - * Factory for the {@link validateAuthSessionId}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link validateAuthSessionId}-function. A corresponding transformFunction has to be supplied. - * @category Authentication - */ -export function _validateAuthSessionIdFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext) => T, -): (context: DvelopContext) => Promise { - return async (context: DvelopContext) => { - const response: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: "/identityprovider", - follows: ["validate"] - }); - return transformFunction(response, context); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + return await response.json(); } /** @@ -83,7 +65,13 @@ export function _validateAuthSessionIdFactory( * ``` * @category Authentication */ -/* istanbul ignore next */ -export async function validateAuthSessionId(context: DvelopContext): Promise { - return await _validateAuthSessionIdFactory(_defaultHttpRequestFunction, _validateAuthSessionIdDefaultTransformFunction)(context); +export async function validateAuthSessionId(context: DvelopContext): Promise; +export async function validateAuthSessionId(context: DvelopContext, options: DvelopOptions): Promise; +export async function validateAuthSessionId( + context: DvelopContext, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, "/identityprovider/validate", { method: "GET" }, options); } diff --git a/packages/identityprovider/src/index.ts b/packages/identityprovider/src/index.ts index abafc3cf..fcb4c0e4 100644 --- a/packages/identityprovider/src/index.ts +++ b/packages/identityprovider/src/index.ts @@ -32,9 +32,8 @@ declare module "@dvelop-sdk/core" { } // Utils -export { DvelopContext, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError } from "@dvelop-sdk/core"; -export { IdentityproviderError } from "./utils/http"; -export * as internals from "./internal"; +export { DvelopContext, DvelopOptions, dvelopFetch, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError } from "@dvelop-sdk/core"; +export { IdentityproviderError } from "./utils/identityprovider-error"; // Authentication export { getAuthSession } from "./authentication/get-auth-session/get-auth-session"; diff --git a/packages/identityprovider/src/internal.ts b/packages/identityprovider/src/internal.ts deleted file mode 100644 index d3f69a7c..00000000 --- a/packages/identityprovider/src/internal.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { _defaultHttpRequestFunctionFactory, _defaultHttpRequestFunction } from "./utils/http"; - -// Authentication -export { _getAuthSessionFactory, _getAuthSessionDefaultTransformFunction } from "./authentication/get-auth-session/get-auth-session"; -export { _getImpersonatedAuthSessionIdFactory, _getImpersonatedAuthSessionIdDefaultTransformFunction } from "./authentication/get-impersonated-auth-session-id/get-impersonated-auth-session-id"; -export { _requestAppSessionFactory } from "./authentication/request-app-session/request-app-session"; -export { _validateAuthSessionIdFactory, _validateAuthSessionIdDefaultTransformFunction } from "./authentication/validate-auth-session-id/validate-auth-session-id"; \ No newline at end of file diff --git a/packages/identityprovider/src/utils/http.spec.ts b/packages/identityprovider/src/utils/http.spec.ts deleted file mode 100644 index 3a8b3d52..00000000 --- a/packages/identityprovider/src/utils/http.spec.ts +++ /dev/null @@ -1,392 +0,0 @@ -import { BadInputError, DvelopContext, DvelopHttpClient, DvelopHttpError, DvelopHttpRequestConfig, DvelopHttpResponse, DvelopSdkError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; -import { _defaultHttpRequestFunctionFactory, IdentityproviderError } from "./http"; - -describe("defaultHttpRequestFunctionFactory", () => { - - let mockRequestFunction = jest.fn(); - let mockHttpClient: DvelopHttpClient = { - request: mockRequestFunction - }; - - let context: DvelopContext; - let config: DvelopHttpRequestConfig; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it("should call request", async () => { - - const response: DvelopHttpResponse = { - data: "HiItsMeResponse" - } as DvelopHttpResponse; - mockRequestFunction.mockResolvedValue(response); - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - const result = await requestFunction(context, config); - - expect(mockRequestFunction).toHaveBeenCalledTimes(1); - expect(result).toBe(response); - }); - - describe("error-handling", () => { - - describe("on statusCode 400", () => { - - it("should throw BadInputError on IdentityproviderErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 400, - data: { - reason: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof BadInputError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).reason); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic BadInputError on missing IdentityproviderErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 400 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof BadInputError).toBeTruthy(); - expect(expectedError.message).toEqual("Identityprovider-App responded with Status 400 indicating bad Request-Parameters. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on statusCode 401", () => { - - it("should throw Unauthorized on IdentityproviderErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 401, - data: { - reason: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof UnauthorizedError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).reason); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw Unauthorized on body", async () => { - - const error: DvelopHttpError = { - response: { - status: 401, - data: "HiItsMeErrorReason" - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: UnauthorizedError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof DvelopSdkError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any)); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic IdentityproviderError on missing Body", async () => { - - const error: DvelopHttpError = { - response: { - status: 401 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof UnauthorizedError).toBeTruthy(); - expect(expectedError.message).toEqual("Identityprovider-App responded with Status 401 indicating bad authSessionId."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on statusCode 403", () => { - - it("should throw ForbiddenError on IdentityproviderErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 403, - data: { - reason: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof ForbiddenError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).reason); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic ForbiddenError on missing IdentityproviderErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 403 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof ForbiddenError).toBeTruthy(); - expect(expectedError.message).toEqual("Identityprovider-App responded with Status 403 indicating a forbidden action. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on statusCode 404", () => { - - it("should throw NotFoundError on IdentityproviderErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 404, - data: { - reason: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotFoundError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).reason); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw NotFoundError on LocalizedMessage", async () => { - - const error: DvelopHttpError = { - response: { - status: 404, - data: { - LocalizedMessage: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotFoundError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).LocalizedMessage); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic BadInputError on missing IdentityproviderErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 404 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotFoundError).toBeTruthy(); - expect(expectedError.message).toEqual("Identityprovider-App responded with Status 404 indicating a requested resource does not exist. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on unknown statusCode", () => { - - it("should throw IdentityproviderError on IdentityproviderErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 500, - data: { - reason: "HiItsMeErrorReason" - } - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof IdentityproviderError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any).reason); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic IdentityproviderError on missing IdentityproviderErrorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 500 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof IdentityproviderError).toBeTruthy(); - expect(expectedError.message).toEqual(`Identityprovider-App responded with status ${error.response.status}. See 'originalError'-property for details.`); - expect(expectedError.originalError).toBe(error); - }); - }); - - - - - it("should throw generic IdentityproviderError on no response", async () => { - const error = new Error("HiItsMeError"); - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof IdentityproviderError).toBeTruthy(); - expect(expectedError.message).toEqual(`Request to Identityprovider-App failed: ${error.message}. See 'originalError'-property for details.`); - expect(expectedError.originalError).toBe(error); - }); - - - }); - -}); \ No newline at end of file diff --git a/packages/identityprovider/src/utils/http.ts b/packages/identityprovider/src/utils/http.ts deleted file mode 100644 index eae7ad5e..00000000 --- a/packages/identityprovider/src/utils/http.ts +++ /dev/null @@ -1,63 +0,0 @@ - -import { DvelopContext, DvelopHttpRequestConfig, DvelopHttpResponse, DvelopHttpClient, defaultDvelopHttpClientFactory, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError, DvelopSdkError } from "@dvelop-sdk/core"; -export { DvelopHttpRequestConfig as HttpConfig, DvelopHttpResponse as HttpResponse } from "@dvelop-sdk/core"; - -/** -* Generic Error for identityprovider-package. -* @category Error -*/ -/* istanbul ignore next */ -export class IdentityproviderError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars - constructor(public message: string, public originalError?: Error) { - super(message); - Object.setPrototypeOf(this, IdentityproviderError.prototype); - } -} - -/** - * Factory used to create the default httpRequestFunction. Mostly used for HTTP-Error handling. - * @internal - * @category Http - */ -export function _defaultHttpRequestFunctionFactory(httpClient: DvelopHttpClient): (context: DvelopContext, config: DvelopHttpRequestConfig) => Promise { - return async (context: DvelopContext, config: DvelopHttpRequestConfig) => { - - try { - return await httpClient.request(context, config); - } catch (error: any) { - - if (error.response) { - - switch (error.response.status) { - case 400: - throw new BadInputError(error.response.data?.reason || "Identityprovider-App responded with Status 400 indicating bad Request-Parameters. See 'originalError'-property for details.", error); - - case 401: - throw new UnauthorizedError(error.response.data?.reason || error.response.data || "Identityprovider-App responded with Status 401 indicating bad authSessionId.", error); - - case 403: - throw new ForbiddenError(error.response.data?.reason || "Identityprovider-App responded with Status 403 indicating a forbidden action. See 'originalError'-property for details.", error); - - case 404: - throw new NotFoundError(error.response.data?.reason || error.response.data?.LocalizedMessage || "Identityprovider-App responded with Status 404 indicating a requested resource does not exist. See 'originalError'-property for details.", error); - - default: - throw new IdentityproviderError(error.response.data?.reason || `Identityprovider-App responded with status ${error.response.status}. See 'originalError'-property for details.`, error); - } - } else { - throw new IdentityproviderError(`Request to Identityprovider-App failed: ${error.message}. See 'originalError'-property for details.`, error); - } - } - }; -} - -/** - * Default httpRequestFunction used in dms-package. - * @internal - * @category Http - */ -/* istanbul ignore next */ -export async function _defaultHttpRequestFunction(context: DvelopContext, config: DvelopHttpRequestConfig): Promise { - return _defaultHttpRequestFunctionFactory(defaultDvelopHttpClientFactory())(context, config); -} \ No newline at end of file diff --git a/packages/identityprovider/src/utils/identityprovider-error.spec.ts b/packages/identityprovider/src/utils/identityprovider-error.spec.ts new file mode 100644 index 00000000..6f3755cf --- /dev/null +++ b/packages/identityprovider/src/utils/identityprovider-error.spec.ts @@ -0,0 +1,87 @@ +import { BadInputError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; +import { IdentityproviderError, ensureSuccessResponse } from "./identityprovider-error"; + +describe("ensureSuccessResponse", () => { + + it("should not throw on 200", async () => { + const response = new Response("anything", { status: 200 }); + await expect(ensureSuccessResponse(response)).resolves.toBeUndefined(); + }); + + it("should not throw on 204", async () => { + const response = new Response(null, { status: 204 }); + await expect(ensureSuccessResponse(response)).resolves.toBeUndefined(); + }); + + describe("on statusCode 400", () => { + + it("should throw BadInputError with reason from body", async () => { + const response = new Response(JSON.stringify({ reason: "HiItsMeErrorReason" }), { + status: 400, + headers: { "Content-Type": "application/json" } + }); + + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(BadInputError); + expect(err.message).toEqual("HiItsMeErrorReason"); + }); + + it("should throw generic BadInputError on missing body", async () => { + const response = new Response(null, { status: 400 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(BadInputError); + expect(err.message).toEqual("Identityprovider-App responded with Status 400 indicating bad Request-Parameters."); + }); + }); + + describe("on statusCode 401", () => { + + it("should throw UnauthorizedError with string body", async () => { + const response = new Response("HiItsMeErrorReason", { status: 401 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(UnauthorizedError); + expect(err.message).toEqual("HiItsMeErrorReason"); + }); + + it("should throw generic UnauthorizedError on missing body", async () => { + const response = new Response(null, { status: 401 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(UnauthorizedError); + expect(err.message).toEqual("Identityprovider-App responded with Status 401 indicating bad authSessionId."); + }); + }); + + it("should throw generic ForbiddenError on statusCode 403", async () => { + const response = new Response(null, { status: 403 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(ForbiddenError); + expect(err.message).toEqual("Identityprovider-App responded with Status 403 indicating a forbidden action."); + }); + + describe("on statusCode 404", () => { + + it("should throw NotFoundError with LocalizedMessage from body", async () => { + const response = new Response(JSON.stringify({ LocalizedMessage: "HiItsMeLocalizedMessage" }), { + status: 404, + headers: { "Content-Type": "application/json" } + }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(NotFoundError); + expect(err.message).toEqual("HiItsMeLocalizedMessage"); + }); + + it("should throw generic NotFoundError on missing body", async () => { + const response = new Response(null, { status: 404 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(NotFoundError); + expect(err.message).toEqual("Identityprovider-App responded with Status 404 indicating a requested resource does not exist."); + }); + }); + + it("should throw generic IdentityproviderError on unknown status code", async () => { + const response = new Response(null, { status: 500 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(IdentityproviderError); + expect(err.message).toEqual("Identityprovider-App responded with status 500."); + }); +}); diff --git a/packages/identityprovider/src/utils/identityprovider-error.ts b/packages/identityprovider/src/utils/identityprovider-error.ts new file mode 100644 index 00000000..7b44b73d --- /dev/null +++ b/packages/identityprovider/src/utils/identityprovider-error.ts @@ -0,0 +1,53 @@ +import { BadInputError, DvelopSdkError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; + +/** + * Generic Error for identityprovider-package. + * @category Error + */ +/* istanbul ignore next */ +export class IdentityproviderError extends DvelopSdkError { + + constructor(public message: string, public originalError?: Error) { + super(message); + Object.setPrototypeOf(this, IdentityproviderError.prototype); + } +} + +/** + * Throws a typed error if the response indicates failure. + * @internal + * @category Http + */ +export async function ensureSuccessResponse(response: Response): Promise { + + if (response.ok) return; + + let body: any; + try { + body = await response.clone().json(); + } catch { + try { + body = await response.clone().text(); + } catch { + body = undefined; + } + } + + let reason: string | undefined; + if (body) { + reason = typeof body === "string" ? body : (body.reason ?? body.LocalizedMessage ?? undefined); + } + + switch (response.status) { + case 400: + throw new BadInputError(reason ?? "Identityprovider-App responded with Status 400 indicating bad Request-Parameters."); + case 401: + throw new UnauthorizedError(reason ?? "Identityprovider-App responded with Status 401 indicating bad authSessionId."); + case 403: + throw new ForbiddenError(reason ?? "Identityprovider-App responded with Status 403 indicating a forbidden action."); + case 404: + throw new NotFoundError(reason ?? "Identityprovider-App responded with Status 404 indicating a requested resource does not exist."); + default: + throw new IdentityproviderError(reason ?? `Identityprovider-App responded with status ${response.status}.`); + } +} diff --git a/packages/identityprovider/tsconfig.build.json b/packages/identityprovider/tsconfig.build.json new file mode 100644 index 00000000..0c9b67d5 --- /dev/null +++ b/packages/identityprovider/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig-base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.spec.ts", "src/**/tmp-*.ts"] +} diff --git a/packages/identityprovider/tsconfig.json b/packages/identityprovider/tsconfig.json index 1e5dc209..3912f472 100644 --- a/packages/identityprovider/tsconfig.json +++ b/packages/identityprovider/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "outDir": "lib", /* Redirect output structure to the directory. */ - "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "noEmit": true, + "composite": false, + "declaration": false, + "declarationMap": false }, - "include": ["src/**/*"], - "exclude": ["src/**/*.spec.ts"] -} \ No newline at end of file + "include": ["src/**/*"] +} diff --git a/packages/logging/package.json b/packages/logging/package.json index c68a63f0..4533caa5 100644 --- a/packages/logging/package.json +++ b/packages/logging/package.json @@ -1,7 +1,7 @@ { "name": "@dvelop-sdk/logging", "description": "This package contains functions for logging with OpenTelemetry.", - "version": "1.0.9", + "version": "1.1.0", "license": "Apache-2.0", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,6 +20,6 @@ "access": "public" }, "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } } diff --git a/packages/logging/src/logger/logger.spec.ts b/packages/logging/src/logger/logger.spec.ts index 11ec7c9d..003dc50b 100644 --- a/packages/logging/src/logger/logger.spec.ts +++ b/packages/logging/src/logger/logger.spec.ts @@ -10,7 +10,7 @@ describe("DvelopLogger", () => { }); test("should throw error on new DvelopLogger if no provider specified", () => { - expect(() => new DvelopLogger({ providers: [] })).toThrowError(); + expect(() => new DvelopLogger({ providers: [] })).toThrow(); }); describe("severity function", () => { diff --git a/packages/logging/src/logger/logger.ts b/packages/logging/src/logger/logger.ts index b7efc162..31fdb14c 100644 --- a/packages/logging/src/logger/logger.ts +++ b/packages/logging/src/logger/logger.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-dupe-class-members */ + import { deepMergeObjects, DvelopContext } from "@dvelop-sdk/core"; import { DvelopLogEvent, DvelopLogLevel } from "./log-event"; diff --git a/packages/logging/src/otelProvider/otelProvider.spec.ts b/packages/logging/src/otelProvider/otelProvider.spec.ts index 2719d6ad..082a4e74 100644 --- a/packages/logging/src/otelProvider/otelProvider.spec.ts +++ b/packages/logging/src/otelProvider/otelProvider.spec.ts @@ -44,7 +44,7 @@ describe("otel provider", () => { return new Promise(done => { const provider = otelProviderFactory({ appName: "test", transports: [async (otelMessage) => { - expect(() => JSON.parse(otelMessage)).not.toThrowError(); + expect(() => JSON.parse(otelMessage)).not.toThrow(); done(); }] }); @@ -108,8 +108,7 @@ describe("otel provider", () => { { level: "debug", otelSeverity: OtelSeverity.DEBUG1 }, { level: "info", otelSeverity: OtelSeverity.INFO1 }, // { level: "warn", otelSeverity: OtelSeverity.WARN1 }, - { level: "error", otelSeverity: OtelSeverity.ERROR1 }, - { level: undefined, otelSeverity: 0 } + { level: "error", otelSeverity: OtelSeverity.ERROR1 } ])("Severity.$otelSeverity", ({ level, otelSeverity }) => { return new Promise(done => { diff --git a/packages/logging/src/otelProvider/otelProvider.ts b/packages/logging/src/otelProvider/otelProvider.ts index 31ce7567..51ffdbb7 100644 --- a/packages/logging/src/otelProvider/otelProvider.ts +++ b/packages/logging/src/otelProvider/otelProvider.ts @@ -9,7 +9,7 @@ import { LoggingError } from "../error"; * @category Error */ export class OtelProviderError extends LoggingError { - // eslint-disable-next-line no-unused-vars + constructor(message: string, originalError?: Error) { super(message, originalError); Object.setPrototypeOf(this, OtelProviderError.prototype); @@ -191,7 +191,5 @@ function mapSeverity(level: DvelopLogLevel): OtelSeverity { return OtelSeverity.INFO1; case "error": return OtelSeverity.ERROR1; - default: - return 0; } } diff --git a/packages/logging/tsconfig.build.json b/packages/logging/tsconfig.build.json new file mode 100644 index 00000000..0c9b67d5 --- /dev/null +++ b/packages/logging/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig-base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.spec.ts", "src/**/tmp-*.ts"] +} diff --git a/packages/logging/tsconfig.json b/packages/logging/tsconfig.json index 5ad7fa35..3912f472 100644 --- a/packages/logging/tsconfig.json +++ b/packages/logging/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "outDir": "lib", /* Redirect output structure to the directory. */ - "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "noEmit": true, + "composite": false, + "declaration": false, + "declarationMap": false }, - "include": ["src/**/*"], - "exclude": ["src/**/*.spec.ts"], -} \ No newline at end of file + "include": ["src/**/*"] +} diff --git a/packages/task/package.json b/packages/task/package.json index 63dc6efd..466ba566 100644 --- a/packages/task/package.json +++ b/packages/task/package.json @@ -1,7 +1,7 @@ { "name": "@dvelop-sdk/task", "description": "This package contains functionality for the Task-App in the d.velop cloud.", - "version": "3.1.5", + "version": "4.0.0", "license": "Apache-2.0", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,6 +20,6 @@ "access": "public" }, "dependencies": { - "@dvelop-sdk/core": "^2.2.3" + "@dvelop-sdk/core": "^3.0.0" } } diff --git a/packages/task/src/index.ts b/packages/task/src/index.ts index 0eb2be23..b6fe9d71 100644 --- a/packages/task/src/index.ts +++ b/packages/task/src/index.ts @@ -23,10 +23,11 @@ * @module task */ -export { DvelopContext, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError } from "@dvelop-sdk/core"; -export { TaskValidation, InvalidTaskDefinitionError, TaskError } from "./utils/http"; -export * as internals from "./internal"; +// Utils +export { DvelopContext, DvelopOptions, dvelopFetch, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError } from "@dvelop-sdk/core"; +export { TaskValidation, InvalidTaskDefinitionError, TaskError } from "./utils/task-error"; +// Tasks export { CreateTaskParams, createTask } from "./tasks/create-task/create-task"; export { CompleteTaskParams, completeTask } from "./tasks/complete-task/complete-task"; export { DeleteTaskParams, deleteTask } from "./tasks/delete-task/delete-task"; diff --git a/packages/task/src/internal.ts b/packages/task/src/internal.ts deleted file mode 100644 index 826dc171..00000000 --- a/packages/task/src/internal.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { _defaultHttpRequestFunctionFactory, _defaultHttpRequestFunction } from "./utils/http"; - -export { _completeTaskFactory } from "./tasks/complete-task/complete-task"; -export { _createTaskFactory, _createTaskDefaultTransformFunction } from "./tasks/create-task/create-task"; -export { _deleteTaskFactory } from "./tasks/delete-task/delete-task"; -export { _getTaskCountFactory, _getTaskCountDefaultTransformFunction } from "./tasks/get-task-count/get-task-count"; -export { _updateTaskFactory } from "./tasks/update-task/update-task"; \ No newline at end of file diff --git a/packages/task/src/tasks/complete-task/complete-task.spec.ts b/packages/task/src/tasks/complete-task/complete-task.spec.ts index 20a9a66a..130d31b2 100644 --- a/packages/task/src/tasks/complete-task/complete-task.spec.ts +++ b/packages/task/src/tasks/complete-task/complete-task.spec.ts @@ -1,78 +1,60 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpResponse, TaskError } from "../../utils/http"; -import { CompleteTaskParams, _completeTaskFactory } from "./complete-task"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { CompleteTaskParams, onResponse, completeTask } from "./complete-task"; +import { TaskError } from "../../utils/task-error"; -describe("completeTaskFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; + +describe("completeTask", () => { let context: DvelopContext; let params: CompleteTaskParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - - params = { - location: "/task/tasks/HiItsMeLocation" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; + params = { location: "/task/tasks/HiItsMeLocation" }; }); - it("should make correct request", async () => { - - const completeTask = _completeTaskFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method POST and completion body", async () => { await completeTask(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "POST", - url: "/task/tasks/HiItsMeLocation/completionState", - data: { - complete: true - } - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe("/task/tasks/HiItsMeLocation/completionState"); + expect(calledInit).toMatchObject({ method: "POST" }); + expect(JSON.parse(calledInit!.body as string)).toEqual({ complete: true }); + expect(calledOptions).toMatchObject({ onResponse: onResponse }); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const completeTask = _completeTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await completeTask(context, params); + it("should work with location with request parameters", async () => { + await completeTask(context, { location: "/task/tasks/HiItsMeLocation?foo=bar&baz=foo" }); - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch.mock.calls[0][1]).toBe("/task/tasks/HiItsMeLocation/completionState"); }); - it("invalid location should throw an error", async () => { - const completeTask = _completeTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await expect(() => completeTask(context, { - location: "/some/faulty/location" - })).rejects.toThrow(TaskError); + it("should throw TaskError on invalid location", async () => { + await expect(() => completeTask(context, { location: "/some/faulty/location" })).rejects.toThrow(TaskError); + expect(mockDvelopFetch).not.toHaveBeenCalled(); }); - it("should work with location with request parameters", async () => { + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await completeTask(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - const completeTask = _completeTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await completeTask(context, { - location: "/task/tasks/HiItsMeLocation?foo=bar&baz=foo" - }); + describe("onResponse", () => { - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "POST", - url: "/task/tasks/HiItsMeLocation/completionState", - data: { - complete: true - } + it("should resolve on success", async () => { + const response = new Response(null, { status: 200 }); + await expect(onResponse(response)).resolves.toBeUndefined(); }); }); -}); \ No newline at end of file +}); diff --git a/packages/task/src/tasks/complete-task/complete-task.ts b/packages/task/src/tasks/complete-task/complete-task.ts index 90332e8b..fcddbc21 100644 --- a/packages/task/src/tasks/complete-task/complete-task.ts +++ b/packages/task/src/tasks/complete-task/complete-task.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import {HttpConfig, HttpResponse, _defaultHttpRequestFunction, TaskError} from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse, TaskError } from "../../utils/task-error"; /** * Parameters for the {@link completeTask}-function. @@ -11,32 +11,12 @@ export interface CompleteTaskParams { } /** - * Factory for the {@link completeTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link completeTask}-function. A corresponding transformFunction has to be supplied. + * Default `onResponse` provided to the {@link completeTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Task */ -export function _completeTaskFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: CompleteTaskParams) => T, -): (context: DvelopContext, params: CompleteTaskParams) => Promise { - return async (context: DvelopContext, params: CompleteTaskParams) => { - const matches: RegExpExecArray | null = /^\/task\/tasks\/(?[^?]*)\??.*$/i.exec(params.location); - if (matches) { - const id = matches.groups?.id; - - const response: HttpResponse = await httpRequestFunction(context, { - method: "POST", - url: `/task/tasks/${id}/completionState`, - data: { - complete: true - } - }); - return transformFunction(response, context, params); - } else { - throw new TaskError(`Failed to parse task id from '${params.location}'`); - } - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } /** @@ -55,7 +35,24 @@ export function _completeTaskFactory( * * @category Task */ -/* istanbul ignore next */ -export function completeTask(context: DvelopContext, params: CompleteTaskParams): Promise { - return _completeTaskFactory(_defaultHttpRequestFunction, () => { })(context, params); -} \ No newline at end of file +export async function completeTask(context: DvelopContext, params: CompleteTaskParams): Promise; +export async function completeTask(context: DvelopContext, params: CompleteTaskParams, options: DvelopOptions): Promise; +export async function completeTask( + context: DvelopContext, + params: CompleteTaskParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + + const matches: RegExpExecArray | null = /^\/task\/tasks\/([^?]*)\??.*$/i.exec(params.location); + if (!matches) { + throw new TaskError(`Failed to parse task id from '${params.location}'`); + } + const id = matches[1]; + + return dvelopFetch(context, `/task/tasks/${id}/completionState`, { + method: "POST", + body: JSON.stringify({ complete: true }) + }, options); +} diff --git a/packages/task/src/tasks/create-task/create-task.spec.ts b/packages/task/src/tasks/create-task/create-task.spec.ts index cc5fac5a..4cfd287f 100644 --- a/packages/task/src/tasks/create-task/create-task.spec.ts +++ b/packages/task/src/tasks/create-task/create-task.spec.ts @@ -1,198 +1,107 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpResponse } from "../../utils/http"; -import { CreateTaskParams, _createTaskDefaultTransformFunction, _createTaskFactory } from "./create-task"; +import { DvelopContext, dvelopFetch, generateRequestId } from "@dvelop-sdk/core"; +import { CreateTaskParams, onResponse, createTask } from "./create-task"; -interface TestCase { - params: CreateTaskParams; - mockedUuidGenerator?: () => string; - expectedData: any; -} +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn(), generateRequestId: jest.fn() }; +}); -describe("createTaskFactory", () => { +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; +const mockGenerateRequestId = generateRequestId as jest.MockedFunction; - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("createTask", () => { let context: DvelopContext; let params: CreateTaskParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + mockGenerateRequestId.mockReturnValue("HiItsMeGeneratedCorrelationKey"); + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { subject: "HiItsMeSubject", assignees: ["HiItsMeAssignee1", "HiItsMeAssignee2"] }; }); - const testCases: TestCase[] = [ - { - params: { - subject: "HiItsMeSubject", - assignees: ["HiItsMeAssignee1", "HiItsMeAssignee2"] - }, - expectedData: { - subject: "HiItsMeSubject", - assignees: ["HiItsMeAssignee1", "HiItsMeAssignee2"], - } - }, - { - params: { - subject: "HiItsMeSubject", - assignees: ["HiItsMeAssignee1", "HiItsMeAssignee2"], - correlationKey: "HiItsMeCorrelationKey" - }, - expectedData: { - subject: "HiItsMeSubject", - assignees: ["HiItsMeAssignee1", "HiItsMeAssignee2"], - correlationKey: "HiItsMeCorrelationKey" - } - }, - { - params: { - subject: "HiItsMeSubject", - assignees: ["HiItsMeAssignee1", "HiItsMeAssignee2"], - correlationKey: "HiItsMeCorrelationKey" - }, - mockedUuidGenerator: () => "HiItsMeGeneratedCorrelationKey", - expectedData: { - subject: "HiItsMeSubject", - assignees: ["HiItsMeAssignee1", "HiItsMeAssignee2"], - correlationKey: "HiItsMeCorrelationKey" - } - }, - { - params: { - subject: "HiItsMeSubject", - assignees: ["HiItsMeAssignee1", "HiItsMeAssignee2"], - }, - mockedUuidGenerator: () => "HiItsMeGeneratedCorrelationKey", - expectedData: { - subject: "HiItsMeSubject", - assignees: ["HiItsMeAssignee1", "HiItsMeAssignee2"], - correlationKey: "HiItsMeGeneratedCorrelationKey" - } - }, - { - params: { - subject: "HiItsMeSubject", - assignees: ["HiItsMeAssignee1", "HiItsMeAssignee2"], - actionScopes: { - complete: ["details"], - claim: ["list"], - forward: ["details", "list"] - } - }, - mockedUuidGenerator: () => "HiItsMeGeneratedCorrelationKey", - expectedData: { - subject: "HiItsMeSubject", - assignees: ["HiItsMeAssignee1", "HiItsMeAssignee2"], - correlationKey: "HiItsMeGeneratedCorrelationKey", - actionScopes: { - complete: ["details"], - claim: ["list"], - forward: ["details", "list"] - } - } - }, - ] - - testCases.forEach(testCase => { - it("should make correct request", async () => { - - const createTask = _createTaskFactory(mockHttpRequestFunction, mockTransformFunction, testCase.mockedUuidGenerator); - await createTask(context, testCase.params); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "POST", - url: "/task/tasks", - data: testCase.expectedData - }); - }); - }); + function calledBody(): any { + return JSON.parse(mockDvelopFetch.mock.calls[0][2]!.body as string); + } - it("should parse dueDate", async () => { + it("should call dvelopFetch with method POST to /task/tasks", async () => { + await createTask(context, params); - const date: Date = new Date(); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe("/task/tasks"); + expect(calledInit).toMatchObject({ method: "POST" }); + expect(calledOptions).toMatchObject({ onResponse: onResponse }); + }); - const updateTask = _createTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await updateTask(context, { ...params, ...{ dueDate: date } }); + it("should generate a correlationKey when none is given", async () => { + await createTask(context, params); + expect(calledBody().correlationKey).toEqual("HiItsMeGeneratedCorrelationKey"); + }); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, expect.objectContaining({ - data: expect.objectContaining({ - dueDate: date.toISOString() - }) - })); + it("should keep a caller-supplied correlationKey", async () => { + await createTask(context, { ...params, correlationKey: "HiItsMeCorrelationKey" }); + expect(mockGenerateRequestId).not.toHaveBeenCalled(); + expect(calledBody().correlationKey).toEqual("HiItsMeCorrelationKey"); }); - it("should parse reminderDate", async () => { + it("should send subject and assignees", async () => { + await createTask(context, params); + const body = calledBody(); + expect(body.subject).toEqual("HiItsMeSubject"); + expect(body.assignees).toEqual(["HiItsMeAssignee1", "HiItsMeAssignee2"]); + }); - const date: Date = new Date(); + it("should send actionScopes", async () => { + const actionScopes: CreateTaskParams["actionScopes"] = { complete: ["details"], claim: ["list"], forward: ["details", "list"] }; + await createTask(context, { ...params, actionScopes }); + expect(calledBody().actionScopes).toEqual(actionScopes); + }); - const updateTask = _createTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await updateTask(context, { ...params, ...{ reminderDate: date } }); + it("should parse dueDate", async () => { + const date: Date = new Date(); + await createTask(context, { ...params, dueDate: date }); + expect(calledBody().dueDate).toEqual(date.toISOString()); + }); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, expect.objectContaining({ - data: expect.objectContaining({ - reminderDate: date.toISOString() - }) - })); + it("should parse reminderDate", async () => { + const date: Date = new Date(); + await createTask(context, { ...params, reminderDate: date }); + expect(calledBody().reminderDate).toEqual(date.toISOString()); }); it("should parse dmsObject", async () => { - - const dmsObject: any = { - repositoryId: "HiItsMeRepoId", - dmsObjectId: "HiItsMeDmsObjectId" - }; - - const createTask = _createTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await createTask(context, { ...params, ...{ dmsObject: dmsObject } }); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, expect.objectContaining({ - data: expect.objectContaining({ - dmsReferences: [{ - repoId: dmsObject.repositoryId, - objectId: dmsObject.dmsObjectId - }] - }) - })); + const dmsObject = { repositoryId: "HiItsMeRepoId", dmsObjectId: "HiItsMeDmsObjectId" }; + await createTask(context, { ...params, dmsObject }); + expect(calledBody().dmsReferences).toEqual([{ + repoId: dmsObject.repositoryId, + objectId: dmsObject.dmsObjectId + }]); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const createTask = _createTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await createTask(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await createTask(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - describe("createTaskDefaultTransformFunction", () => { - - it("should map correctly", async () => { + describe("onResponse", () => { - const location: string = "HiItsMeLocation"; - mockHttpRequestFunction.mockResolvedValue({ headers: { "location": location } } as unknown as HttpResponse); - - const createTask = _createTaskFactory(mockHttpRequestFunction, _createTaskDefaultTransformFunction); - const result: string = await createTask(context, params); + it("should return the location header", async () => { + const response = new Response(null, { status: 201, headers: { location: "HiItsMeLocation" } }); + const result: string = await onResponse(response); + expect(result).toEqual("HiItsMeLocation"); + }); - expect(result).toEqual(location); + it("should return an empty string when no location header is present", async () => { + const response = new Response(null, { status: 201 }); + const result: string = await onResponse(response); + expect(result).toEqual(""); }); }); -}); \ No newline at end of file +}); diff --git a/packages/task/src/tasks/create-task/create-task.ts b/packages/task/src/tasks/create-task/create-task.ts index 2ef5a6a4..6482a6f4 100644 --- a/packages/task/src/tasks/create-task/create-task.ts +++ b/packages/task/src/tasks/create-task/create-task.ts @@ -1,5 +1,5 @@ -import { DvelopContext, generateRequestId } from "@dvelop-sdk/core"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch, generateRequestId } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/task-error"; /** * Parameters for the {@link createTask}-function. @@ -85,57 +85,13 @@ export interface CreateTaskParams { } /** - * Default transform-function provided to the {@link createTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * Default `onResponse` provided to the {@link createTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Task */ -export function _createTaskDefaultTransformFunction(response: HttpResponse, _: DvelopContext, __: CreateTaskParams): string { - return response.headers["location"] || ""; -} - -/** - * Factory for the {@link createTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link createTask}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category Task - */ -export function _createTaskFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: CreateTaskParams) => T, - uuidGeneratorFunction?: () => string -): (context: DvelopContext, params: CreateTaskParams) => Promise { - - return async (context: DvelopContext, params: CreateTaskParams) => { - - const task: any = { ...params }; - - if (uuidGeneratorFunction && !params.correlationKey) { - task.correlationKey = uuidGeneratorFunction(); - } - - if (params.dueDate) { - task.dueDate = params.dueDate.toISOString(); - } - - if (params.reminderDate) { - task.reminderDate = params.reminderDate.toISOString(); - } - - if (params.dmsObject) { - task.dmsReferences = [{ - repoId: params.dmsObject.repositoryId, - objectId: params.dmsObject.dmsObjectId - }]; - } - - const response: HttpResponse = await httpRequestFunction(context, { - method: "POST", - url: "/task/tasks", - data: task - }); - - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + return response.headers.get("location") ?? ""; } /** @@ -158,7 +114,39 @@ export function _createTaskFactory( * * @category Task */ -/* istanbul ignore next */ -export function createTask(context: DvelopContext, params: CreateTaskParams): Promise { - return _createTaskFactory(_defaultHttpRequestFunction, _createTaskDefaultTransformFunction, generateRequestId)(context, params); +export async function createTask(context: DvelopContext, params: CreateTaskParams): Promise; +export async function createTask(context: DvelopContext, params: CreateTaskParams, options: DvelopOptions): Promise; +export async function createTask( + context: DvelopContext, + params: CreateTaskParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + + const task: any = { ...params }; + + if (!params.correlationKey) { + task.correlationKey = generateRequestId(); + } + + if (params.dueDate) { + task.dueDate = params.dueDate.toISOString(); + } + + if (params.reminderDate) { + task.reminderDate = params.reminderDate.toISOString(); + } + + if (params.dmsObject) { + task.dmsReferences = [{ + repoId: params.dmsObject.repositoryId, + objectId: params.dmsObject.dmsObjectId + }]; + } + + return dvelopFetch(context, "/task/tasks", { + method: "POST", + body: JSON.stringify(task) + }, options); } \ No newline at end of file diff --git a/packages/task/src/tasks/delete-task/delete-task.spec.ts b/packages/task/src/tasks/delete-task/delete-task.spec.ts index b0489da4..dd44d331 100644 --- a/packages/task/src/tasks/delete-task/delete-task.spec.ts +++ b/packages/task/src/tasks/delete-task/delete-task.spec.ts @@ -1,51 +1,47 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpResponse } from "../../utils/http"; -import { DeleteTaskParams, _deleteTaskFactory } from "./delete-task"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { DeleteTaskParams, onResponse, deleteTask } from "./delete-task"; -describe("deleteTaskFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; + +describe("deleteTask", () => { let context: DvelopContext; let params: DeleteTaskParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - - params = { - location: "HiItsMeLocation" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; + params = { location: "HiItsMeLocation" }; }); - it("should make correct request", async () => { - - const deleteTask = _deleteTaskFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method DELETE", async () => { await deleteTask(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "DELETE", - url: params.location - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + params.location, + { method: "DELETE" }, + expect.objectContaining({ onResponse: onResponse }) + ); }); - it("should pass response to transform and return transform-result", async () => { + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await deleteTask(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); + describe("onResponse", () => { - const deleteTask = _deleteTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await deleteTask(context, params); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should resolve on success", async () => { + const response = new Response(null, { status: 204 }); + await expect(onResponse(response)).resolves.toBeUndefined(); + }); }); -}); \ No newline at end of file +}); diff --git a/packages/task/src/tasks/delete-task/delete-task.ts b/packages/task/src/tasks/delete-task/delete-task.ts index d9dda3eb..8a576a8f 100644 --- a/packages/task/src/tasks/delete-task/delete-task.ts +++ b/packages/task/src/tasks/delete-task/delete-task.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/task-error"; /** * Parameters for the {@link deleteTask}-function. @@ -11,27 +11,16 @@ export interface DeleteTaskParams { } /** - * Factory for the {@link deleteTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link deleteTask}-function. A corresponding transformFunction has to be supplied. + * Default `onResponse` provided to the {@link deleteTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Task */ -export function _deleteTaskFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: DeleteTaskParams) => T, -): (context: DvelopContext, params: DeleteTaskParams) => Promise { - return async (context: DvelopContext, params: DeleteTaskParams) => { - - const response: HttpResponse = await httpRequestFunction(context, { - method: "DELETE", - url: params.location - }); - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } /** - * Mark task as completed. + * Delete a task. * * ```typescript * import { deleteTask } from "@dvelop-sdk/task"; @@ -46,7 +35,14 @@ export function _deleteTaskFactory( * * @category Task */ -/* istanbul ignore next */ -export function deleteTask(context: DvelopContext, params: DeleteTaskParams): Promise { - return _deleteTaskFactory(_defaultHttpRequestFunction, () => { })(context, params); -} \ No newline at end of file +export async function deleteTask(context: DvelopContext, params: DeleteTaskParams): Promise; +export async function deleteTask(context: DvelopContext, params: DeleteTaskParams, options: DvelopOptions): Promise; +export async function deleteTask( + context: DvelopContext, + params: DeleteTaskParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, params.location, { method: "DELETE" }, options); +} diff --git a/packages/task/src/tasks/get-task-count/get-task-count.spec.ts b/packages/task/src/tasks/get-task-count/get-task-count.spec.ts index f53995f8..3200100b 100644 --- a/packages/task/src/tasks/get-task-count/get-task-count.spec.ts +++ b/packages/task/src/tasks/get-task-count/get-task-count.spec.ts @@ -1,63 +1,50 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpResponse } from "../../utils/http"; -import { _getTaskCountDefaultTransformFunction, _getTaskCountFactory } from "./get-task-count"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { onResponse, getTaskCount } from "./get-task-count"; -describe("getTaskCountFactory", () => { +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; + +describe("getTaskCount", () => { let context: DvelopContext; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; }); - it("should make correct request", async () => { - - const getTaskCount = _getTaskCountFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method GET", async () => { await getTaskCount(context); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/task/count/all" - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + "/task/count/all", + { method: "GET" }, + expect.objectContaining({ onResponse: onResponse }) + ); }); - it("should pass response to transform and return transform-result", async () => { - - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); - - const getTaskCount = _getTaskCountFactory(mockHttpRequestFunction, mockTransformFunction); - await getTaskCount(context); - - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await getTaskCount(context, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - describe("getTaskCountDefaultTransformFunction", () => { - - it("should map correctly", async () => { - - const data: any = { - count: 42 - }; + describe("onResponse", () => { - mockHttpRequestFunction.mockResolvedValue({ data: data } as HttpResponse); + it("should map count correctly", async () => { + const response = new Response(JSON.stringify({ count: 42 }), { + status: 200, headers: { "Content-Type": "application/json" } + }); - const getTaskCount = _getTaskCountFactory(mockHttpRequestFunction, _getTaskCountDefaultTransformFunction); - const result: number = await getTaskCount(context); + const result: number = await onResponse(response); - expect(result).toEqual(data.count); + expect(result).toEqual(42); }); }); -}); \ No newline at end of file +}); diff --git a/packages/task/src/tasks/get-task-count/get-task-count.ts b/packages/task/src/tasks/get-task-count/get-task-count.ts index d3401ae7..82fbee10 100644 --- a/packages/task/src/tasks/get-task-count/get-task-count.ts +++ b/packages/task/src/tasks/get-task-count/get-task-count.ts @@ -1,37 +1,19 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/task-error"; /** - * Default transform-function provided to the {@link getTaskCount}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * Default `onResponse` provided to the {@link getTaskCount}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Task */ -export function _getTaskCountDefaultTransformFunction(response: HttpResponse, _: DvelopContext): number { - return response.data.count; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + const data: any = await response.json(); + return data.count; } /** - * Factory for the {@link getTaskCount}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link getTaskCount}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category Task - */ -export function _getTaskCountFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext) => T, -): (context: DvelopContext) => Promise { - return async (context: DvelopContext) => { - - const response: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: "/task/count/all", - }); - return transformFunction(response, context); - }; -} - -/** - * Create a task. + * Get the number of tasks for the current user. * * ```typescript * import { getTaskCount } from "@dvelop-sdk/task"; @@ -44,7 +26,13 @@ export function _getTaskCountFactory( * * @category Task */ -/* istanbul ignore next */ -export function getTaskCount(context: DvelopContext): Promise { - return _getTaskCountFactory(_defaultHttpRequestFunction, _getTaskCountDefaultTransformFunction)(context); -} \ No newline at end of file +export async function getTaskCount(context: DvelopContext): Promise; +export async function getTaskCount(context: DvelopContext, options: DvelopOptions): Promise; +export async function getTaskCount( + context: DvelopContext, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, "/task/count/all", { method: "GET" }, options); +} diff --git a/packages/task/src/tasks/get-task/get-task.spec.ts b/packages/task/src/tasks/get-task/get-task.spec.ts index 178ab24b..5a16ba61 100644 --- a/packages/task/src/tasks/get-task/get-task.spec.ts +++ b/packages/task/src/tasks/get-task/get-task.spec.ts @@ -1,49 +1,70 @@ -import {_getTaskDefaultTransformFunction, _getTaskFactory, GetTaskParams} from "./get-task"; -import {DvelopContext} from "@dvelop-sdk/core"; -import {HttpResponse} from "../../utils/http"; - -describe("getTaskFactory", () => { - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); - - let params : GetTaskParams = { - taskId: "SomeTestId" - }; - let context: DvelopContext = { - systemBaseUri: "someBaseUri" - }; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { GetTaskParams, Task, onResponse, getTask } from "./get-task"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; + +describe("getTask", () => { + + let context: DvelopContext; + let params: GetTaskParams; beforeEach(() => { jest.resetAllMocks(); + context = { systemBaseUri: "someBaseUri" }; + params = { taskId: "SomeTestId" }; }); - - it("should map params correctly", async () => { - const getTask = _getTaskFactory(mockHttpRequestFunction, mockTransformFunction); - + + it("should call dvelopFetch with method GET", async () => { await getTask(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "GET", - url: "/task/tasks/SomeTestId" - }); + + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + expect(mockDvelopFetch).toHaveBeenCalledWith( + context, + "/task/tasks/SomeTestId", + { method: "GET" }, + expect.objectContaining({ onResponse: onResponse }) + ); }); - it("should transform response", async () => { - mockHttpRequestFunction.mockResolvedValue({ data: { - "id" : "SomeTestId", - "subject" : "My subject", - "assignees" : ["bob"], - "sender" : "alice", - "receiveDate" : "2024-07-28T12:12:12.000Z" - }} as unknown as HttpResponse); - const getTask = _getTaskFactory(mockHttpRequestFunction, _getTaskDefaultTransformFunction); - - const task = await getTask(context, params); - - expect(task.id).toBe("SomeTestId"); - expect(task.subject).toBe("My subject"); - expect(task.assignees).toEqual(["bob"]); - expect(task.sender).toBe("alice"); - expect(task.receiveDate).toEqual(new Date("2024-07-28T12:12:12.000Z")); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await getTask(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); -}); \ No newline at end of file + describe("onResponse", () => { + + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } + + it("should map and transform date values", async () => { + const response = jsonResponse({ + id: "SomeTestId", + subject: "My subject", + assignees: ["bob"], + sender: "alice", + receiveDate: "2024-07-28T12:12:12.000Z", + dueDate: "2025-01-01T12:00:00.000Z", + reminderDate: "2026-01-01T12:00:00.000Z", + completionDate: "2027-01-01T12:00:00.000Z" + }); + + const task: Task = await onResponse(response); + + expect(task.id).toBe("SomeTestId"); + expect(task.subject).toBe("My subject"); + expect(task.assignees).toEqual(["bob"]); + expect(task.sender).toBe("alice"); + expect(task.receiveDate).toEqual(new Date("2024-07-28T12:12:12.000Z")); + expect(task.dueDate).toEqual(new Date("2025-01-01T12:00:00.000Z")); + expect(task.reminderDate).toEqual(new Date("2026-01-01T12:00:00.000Z")); + expect(task.completionDate).toEqual(new Date("2027-01-01T12:00:00.000Z")); + }); + }); +}); diff --git a/packages/task/src/tasks/get-task/get-task.ts b/packages/task/src/tasks/get-task/get-task.ts index 125ff060..c0e84ec4 100644 --- a/packages/task/src/tasks/get-task/get-task.ts +++ b/packages/task/src/tasks/get-task/get-task.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/task-error"; /** * Parameters for the {@link getTask}-function. @@ -87,14 +87,14 @@ export interface Task { } /** - * Default transform-function provided to the {@link getTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * Default `onResponse` provided to the {@link getTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Task */ -export function _getTaskDefaultTransformFunction(response: HttpResponse, _: DvelopContext, __: GetTaskParams): Task { - let task : Task; - const responseTask = response.data; - task = {...responseTask}; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); + const responseTask: any = await response.json(); + const task: Task = { ...responseTask }; if (responseTask.receiveDate) { task.receiveDate = new Date(responseTask.receiveDate); @@ -112,27 +112,6 @@ export function _getTaskDefaultTransformFunction(response: HttpResponse, _: Dvel return task; } -/** - * Factory for the {@link getTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link getTask}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category Task - */ -export function _getTaskFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: GetTaskParams) => T -): (context: DvelopContext, params: GetTaskParams) => Promise { - - return async (context: DvelopContext, params: GetTaskParams) => { - const response: HttpResponse = await httpRequestFunction(context, { - method: "GET", - url: `/task/tasks/${params.taskId}` - }); - - return transformFunction(response, context, params); - }; -} - /** * Get a task. * @returns A task object @@ -144,13 +123,20 @@ export function _getTaskFactory( * systemBaseUri: "https://umbrella-corp.d-velop.cloud", * authSessionId: "dQw4w9WgXcQ" * }, { - * id: "SomeTaskId" + * taskId: "SomeTaskId" * }); * ``` * * @category Task */ -/* istanbul ignore next */ -export function getTask(context: DvelopContext, params: GetTaskParams): Promise { - return _getTaskFactory(_defaultHttpRequestFunction, _getTaskDefaultTransformFunction)(context, params); +export async function getTask(context: DvelopContext, params: GetTaskParams): Promise; +export async function getTask(context: DvelopContext, params: GetTaskParams, options: DvelopOptions): Promise; +export async function getTask( + context: DvelopContext, + params: GetTaskParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + return dvelopFetch(context, `/task/tasks/${params.taskId}`, { method: "GET" }, options); } \ No newline at end of file diff --git a/packages/task/src/tasks/search-tasks/search-tasks.spec.ts b/packages/task/src/tasks/search-tasks/search-tasks.spec.ts index 00cec70c..1319ec6e 100644 --- a/packages/task/src/tasks/search-tasks/search-tasks.spec.ts +++ b/packages/task/src/tasks/search-tasks/search-tasks.spec.ts @@ -1,26 +1,22 @@ -import {_searchTasksDefaultTransformFunctionFactory, _searchTasksFactory, buildRangeParameter, SearchTasksParams} from "./search-tasks"; -import {DvelopContext} from "@dvelop-sdk/core"; -import {HttpResponse} from "../../utils/http"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { SearchTasksParams, SearchTasksPage, buildRangeParameter, onResponseFactory, searchTasks } from "./search-tasks"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; describe("build range parameters", () => { it("should create a valid range string", () => { - const result = buildRangeParameter({ - from: 10, - to: 20, - beginInclusive: true, - endInclusive: false - }); - + const result = buildRangeParameter({ from: 10, to: 20, beginInclusive: true, endInclusive: false }); expect(result).toBe("[10..20)"); }); it("should use inclusive search as default", () => { - const result = buildRangeParameter({ - from: 10, - to: 20 - }); - + const result = buildRangeParameter({ from: 10, to: 20 }); expect(result).toBe("[10..20]"); }); @@ -29,10 +25,7 @@ describe("build range parameters", () => { }); it("should work with an open range", () => { - const result = buildRangeParameter({ - to: 20 - }); - + const result = buildRangeParameter({ to: 20 }); expect(result).toBe("[..20]"); }); @@ -41,115 +34,100 @@ describe("build range parameters", () => { from: new Date("2024-01-01T00:00:00.000Z"), to: new Date("2025-01-01T00:00:00.000Z") }); - expect(result).toBe("[2024-01-01T00:00:00.000Z..2025-01-01T00:00:00.000Z]"); }); }); -describe("search tasks", () => { - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("searchTasks", () => { - let context: DvelopContext = { - systemBaseUri: "someBaseUri" - }; + let context: DvelopContext; beforeEach(() => { jest.resetAllMocks(); + context = { systemBaseUri: "someBaseUri" }; }); - it("should call the correct endpoint", async () => { - const searchTasks = _searchTasksFactory(mockHttpRequestFunction, mockTransformFunction); - - let params : SearchTasksParams = {}; + function jsonResponse(data: any): Response { + return new Response(JSON.stringify(data), { status: 200, headers: { "Content-Type": "application/json" } }); + } + it("should call dvelopFetch with method POST to the search endpoint", async () => { + const params: SearchTasksParams = {}; await searchTasks(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "POST", - url: "/task/api/tasks/search", - data: {} - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe("/task/api/tasks/search"); + expect(calledInit).toMatchObject({ method: "POST" }); + expect(JSON.parse(calledInit!.body as string)).toEqual({}); + expect(calledOptions).toMatchObject({ onResponse: expect.any(Function) }); }); - it("should pass parameters", async () => { - const searchTasks = _searchTasksFactory(mockHttpRequestFunction, mockTransformFunction); - - let params : SearchTasksParams = { + it("should pass parameters as body", async () => { + const params: SearchTasksParams = { pageSize: 5, orderBy: "subject", orderDir: "DESC", - filter: { - subject: ["test"] - } + filter: { subject: ["test"] } }; await searchTasks(context, params); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "POST", - url: "/task/api/tasks/search", - data: { - pageSize: 5, - orderBy: "subject", - orderDir: "DESC", - filter: { - subject: ["test"] - } - } - }); + expect(JSON.parse(mockDvelopFetch.mock.calls[0][2]!.body as string)).toEqual(params); }); - it ("should transform date values", async () => { - mockHttpRequestFunction.mockResolvedValue({ data: { - tasks: [{ - id: "test1", - receiveDate: "2024-01-01T12:00:00.000Z", - dueDate: "2025-01-01T12:00:00.000Z", - reminderDate: "2026-01-01T12:00:00.000Z", - completionDate: "2027-01-01T12:00:00.000Z", - state: "COMPLETED" - }] - }} as unknown as HttpResponse); - - const transformFunction = _searchTasksDefaultTransformFunctionFactory(mockHttpRequestFunction); - const searchTasks = _searchTasksFactory(mockHttpRequestFunction, transformFunction); - - let page = await searchTasks(context, {}); - - expect(page.tasks[0].receiveDate).toStrictEqual(new Date("2024-01-01T12:00:00.000Z")); - expect(page.tasks[0].dueDate).toStrictEqual(new Date("2025-01-01T12:00:00.000Z")); - expect(page.tasks[0].reminderDate).toStrictEqual(new Date("2026-01-01T12:00:00.000Z")); - expect(page.tasks[0].completionDate).toStrictEqual(new Date("2027-01-01T12:00:00.000Z")); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await searchTasks(context, {}, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); }); - it("should support paging", async () => { - mockHttpRequestFunction.mockResolvedValue({ data: { - tasks: [], - _links: { - next: {href: "/test/next"} - } - }} as unknown as HttpResponse); + describe("onResponseFactory", () => { + + it("should transform date values", async () => { + const transform = onResponseFactory(context, {}); + const page = await transform(jsonResponse({ + tasks: [{ + id: "test1", + receiveDate: "2024-01-01T12:00:00.000Z", + dueDate: "2025-01-01T12:00:00.000Z", + reminderDate: "2026-01-01T12:00:00.000Z", + completionDate: "2027-01-01T12:00:00.000Z", + state: "COMPLETED" + }] + })); + + expect(page.tasks[0].receiveDate).toStrictEqual(new Date("2024-01-01T12:00:00.000Z")); + expect(page.tasks[0].dueDate).toStrictEqual(new Date("2025-01-01T12:00:00.000Z")); + expect(page.tasks[0].reminderDate).toStrictEqual(new Date("2026-01-01T12:00:00.000Z")); + expect(page.tasks[0].completionDate).toStrictEqual(new Date("2027-01-01T12:00:00.000Z")); + }); - const transformFunction = _searchTasksDefaultTransformFunctionFactory(mockHttpRequestFunction); - const searchTasks = _searchTasksFactory(mockHttpRequestFunction, transformFunction); + it("should not set getNextPage when no next link present", async () => { + const transform = onResponseFactory(context, {}); + const page = await transform(jsonResponse({ tasks: [] })); + expect(page.getNextPage).toBeUndefined(); + }); - const params : SearchTasksParams = { - pageSize: 1 - }; - let searchTasksPage = await searchTasks(context, params); + it("should set getNextPage and follow the next link on invocation", async () => { + const params: SearchTasksParams = { pageSize: 1 }; + const transform = onResponseFactory(context, params); + const page = await transform(jsonResponse({ tasks: [], _links: { next: { href: "/test/next" } } })); - await searchTasksPage.getNextPage(); + expect(page.getNextPage).toEqual(expect.any(Function)); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(2); + const nextPage = { tasks: [] }; + mockDvelopFetch.mockResolvedValueOnce(nextPage as unknown as SearchTasksPage); + await page.getNextPage!(); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "POST", - url: "/test/next", - data: { - pageSize: 1 - } + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit, calledOptions] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe("/test/next"); + expect(calledInit).toMatchObject({ method: "POST" }); + expect(JSON.parse(calledInit!.body as string)).toEqual(params); + expect(calledOptions).toMatchObject({ onResponse: expect.any(Function) }); }); - }); -}); \ No newline at end of file +}); diff --git a/packages/task/src/tasks/search-tasks/search-tasks.ts b/packages/task/src/tasks/search-tasks/search-tasks.ts index 76091567..31770add 100644 --- a/packages/task/src/tasks/search-tasks/search-tasks.ts +++ b/packages/task/src/tasks/search-tasks/search-tasks.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/task-error"; import {Task} from "../get-task/get-task"; /** @@ -177,14 +177,21 @@ export interface SearchTasksPage { } /** - * Factory for the default transform-function provided to the {@link searchTasks}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. + * Factory for the default `onResponse` provided to the {@link searchTasks}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Task */ -export function _searchTasksDefaultTransformFunctionFactory(httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise): (response: HttpResponse, _: DvelopContext, __: SearchTasksParams) => SearchTasksPage { - return (response: HttpResponse, context: DvelopContext, params: SearchTasksParams) => { - let page : SearchTasksPage = { - tasks: response.data.tasks +export function onResponseFactory( + context: DvelopContext, + params: SearchTasksParams +): (response: Response) => Promise { + return async (response: Response) => { + + await ensureSuccessResponse(response); + const data: any = await response.json(); + + const page: SearchTasksPage = { + tasks: data.tasks }; page.tasks.forEach(task => { @@ -202,43 +209,19 @@ export function _searchTasksDefaultTransformFunctionFactory(httpRequestFunction: } }); - if (response.data._links?.next) { - page.getNextPage = async () => { - const nextResponse: HttpResponse = await httpRequestFunction(context, { - method: "POST", - url: response.data._links.next.href, - data: params - }); - return _searchTasksDefaultTransformFunctionFactory(httpRequestFunction)(nextResponse, context, params); - }; + if (data._links?.next) { + page.getNextPage = async () => dvelopFetch(context, data._links.next.href, { + method: "POST", + body: JSON.stringify(params) + }, { + onResponse: onResponseFactory(context, params) + }); } return page; }; } -/** - * Factory for the {@link searchTasks}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link searchTasks}-function. A corresponding transformFunction has to be supplied. - * @internal - * @category Task - */ -export function _searchTasksFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: SearchTasksParams) => T -): (context: DvelopContext, params: SearchTasksParams) => Promise { - - return async (context: DvelopContext, params: SearchTasksParams) => { - const response: HttpResponse = await httpRequestFunction(context, { - method: "POST", - url: "/task/api/tasks/search", - data: params - }); - - return transformFunction(response, context, params); - }; -} - /** * Search for tasks. * @returns A page of matching tasks. @@ -259,7 +242,17 @@ export function _searchTasksFactory( * * @category Task */ -/* istanbul ignore next */ -export function searchTasks(context: DvelopContext, params: SearchTasksParams): Promise { - return _searchTasksFactory(_defaultHttpRequestFunction, _searchTasksDefaultTransformFunctionFactory(_defaultHttpRequestFunction))(context, params); +export async function searchTasks(context: DvelopContext, params: SearchTasksParams): Promise; +export async function searchTasks(context: DvelopContext, params: SearchTasksParams, options: DvelopOptions): Promise; +export async function searchTasks( + context: DvelopContext, + params: SearchTasksParams, + options: DvelopOptions = { + onResponse: onResponseFactory(context, params) + } +): Promise { + return dvelopFetch(context, "/task/api/tasks/search", { + method: "POST", + body: JSON.stringify(params) + }, options); } \ No newline at end of file diff --git a/packages/task/src/tasks/update-task/update-task.spec.ts b/packages/task/src/tasks/update-task/update-task.spec.ts index 0d20cfd6..25078034 100644 --- a/packages/task/src/tasks/update-task/update-task.spec.ts +++ b/packages/task/src/tasks/update-task/update-task.spec.ts @@ -1,28 +1,26 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpResponse } from "../../utils/http"; -import { UpdateTaskParams, _updateTaskFactory } from "./update-task"; +import { DvelopContext, dvelopFetch } from "@dvelop-sdk/core"; +import { UpdateTaskParams, onResponse, updateTask } from "./update-task"; + +jest.mock("@dvelop-sdk/core", () => { + const actual = jest.requireActual("@dvelop-sdk/core"); + return { ...actual, dvelopFetch: jest.fn() }; +}); + +const mockDvelopFetch = dvelopFetch as jest.MockedFunction; interface TestCase { params: UpdateTaskParams; expectedData: any; } -describe("updateTaskFactory", () => { - - let mockHttpRequestFunction = jest.fn(); - let mockTransformFunction = jest.fn(); +describe("updateTask", () => { let context: DvelopContext; let params: UpdateTaskParams; beforeEach(() => { - jest.resetAllMocks(); - - context = { - systemBaseUri: "HiItsMeSystemBaseUri" - }; - + context = { systemBaseUri: "HiItsMeSystemBaseUri" }; params = { location: "HiItsMeLocation", subject: "HiItsMeSubject", @@ -76,86 +74,66 @@ describe("updateTaskFactory", () => { } } }, - ] + ]; testCases.forEach(testCase => { - it("should make correct request", async () => { - - const updateTask = _updateTaskFactory(mockHttpRequestFunction, mockTransformFunction); + it("should call dvelopFetch with method PATCH and correct body", async () => { await updateTask(context, testCase.params); - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, { - method: "PATCH", - url: params.location, - data: testCase.expectedData - }); + expect(mockDvelopFetch).toHaveBeenCalledTimes(1); + const [calledContext, calledUrl, calledInit] = mockDvelopFetch.mock.calls[0]; + expect(calledContext).toBe(context); + expect(calledUrl).toBe(params.location); + expect(calledInit).toMatchObject({ method: "PATCH" }); + expect(JSON.parse(calledInit!.body as string)).toEqual(testCase.expectedData); }); - }) + }); it("should parse dueDate", async () => { - const date: Date = new Date(); + await updateTask(context, { ...params, dueDate: date }); - const updateTask = _updateTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await updateTask(context, { ...params, ...{ dueDate: date } }); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, expect.objectContaining({ - data: expect.objectContaining({ - dueDate: date.toISOString() - }) - })); + const calledInit = mockDvelopFetch.mock.calls[0][2]; + expect(JSON.parse(calledInit!.body as string).dueDate).toEqual(date.toISOString()); }); it("should parse reminderDate", async () => { - const date: Date = new Date(); + await updateTask(context, { ...params, reminderDate: date }); - const updateTask = _updateTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await updateTask(context, { ...params, ...{ reminderDate: date } }); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, expect.objectContaining({ - data: expect.objectContaining({ - reminderDate: date.toISOString() - }) - })); + const calledInit = mockDvelopFetch.mock.calls[0][2]; + expect(JSON.parse(calledInit!.body as string).reminderDate).toEqual(date.toISOString()); }); it("should parse dmsObject", async () => { - - const dmsObject: any = { - repositoryId: "HiItsMeRepoId", - dmsObjectId: "HiItsMeDmsObjectId" - }; - - const updateTask = _updateTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await updateTask(context, { ...params, ...{ dmsObject: dmsObject } }); - - expect(mockHttpRequestFunction).toHaveBeenCalledTimes(1); - expect(mockHttpRequestFunction).toHaveBeenCalledWith(context, expect.objectContaining({ - data: expect.objectContaining({ - dmsReferences: [{ - repoId: dmsObject.repositoryId, - objectId: dmsObject.dmsObjectId - }] - }) - })); + const dmsObject = { repositoryId: "HiItsMeRepoId", dmsObjectId: "HiItsMeDmsObjectId" }; + await updateTask(context, { ...params, dmsObject }); + + const calledInit = mockDvelopFetch.mock.calls[0][2]; + expect(JSON.parse(calledInit!.body as string).dmsReferences).toEqual([{ + repoId: dmsObject.repositoryId, + objectId: dmsObject.dmsObjectId + }]); }); + it("should not include location in the body", async () => { + await updateTask(context, params); - it("should pass response to transform and return transform-result", async () => { + const calledInit = mockDvelopFetch.mock.calls[0][2]; + expect(JSON.parse(calledInit!.body as string).location).toBeUndefined(); + }); - const response: HttpResponse = { data: { test: "HiItsMeTest" } } as HttpResponse; - const transformResult: any = { result: "HiItsMeResult" }; - mockHttpRequestFunction.mockResolvedValue(response); - mockTransformFunction.mockReturnValue(transformResult); + it("should forward caller-supplied options", async () => { + const options = { onResponse: jest.fn() }; + await updateTask(context, params, options); + expect(mockDvelopFetch.mock.calls[0][3]).toBe(options); + }); - const updateTask = _updateTaskFactory(mockHttpRequestFunction, mockTransformFunction); - await updateTask(context, params); + describe("onResponse", () => { - expect(mockTransformFunction).toHaveBeenCalledTimes(1); - expect(mockTransformFunction).toHaveBeenCalledWith(response, context, params); + it("should resolve on success", async () => { + const response = new Response(null, { status: 200 }); + await expect(onResponse(response)).resolves.toBeUndefined(); + }); }); -}); \ No newline at end of file +}); diff --git a/packages/task/src/tasks/update-task/update-task.ts b/packages/task/src/tasks/update-task/update-task.ts index a8f8d122..91a3b06f 100644 --- a/packages/task/src/tasks/update-task/update-task.ts +++ b/packages/task/src/tasks/update-task/update-task.ts @@ -1,5 +1,5 @@ -import { DvelopContext } from "@dvelop-sdk/core"; -import { HttpConfig, HttpResponse, _defaultHttpRequestFunction } from "../../utils/http"; +import { DvelopContext, DvelopOptions, dvelopFetch } from "@dvelop-sdk/core"; +import { ensureSuccessResponse } from "../../utils/task-error"; /** * Parameters for the {@link updateTask}-function. @@ -86,43 +86,12 @@ export interface UpdateTaskParams { } /** - * Factory for the {@link updateTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @typeparam T Return type of the {@link updateTask}-function. A corresponding transformFunction has to be supplied. + * Default `onResponse` provided to the {@link updateTask}-function. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. * @internal * @category Task */ -export function _updateTaskFactory( - httpRequestFunction: (context: DvelopContext, config: HttpConfig) => Promise, - transformFunction: (response: HttpResponse, context: DvelopContext, params: UpdateTaskParams) => T, -): (context: DvelopContext, params: UpdateTaskParams) => Promise { - - return async (context: DvelopContext, params: UpdateTaskParams) => { - - const task: any = { ...params }; - delete task.location; - - if (params.dueDate) { - task.dueDate = params.dueDate.toISOString(); - } - - if (params.reminderDate) { - task.reminderDate = params.reminderDate.toISOString(); - } - - if (params.dmsObject) { - task.dmsReferences = [{ - repoId: params.dmsObject.repositoryId, - objectId: params.dmsObject.dmsObjectId - }]; - } - - const response: HttpResponse = await httpRequestFunction(context, { - method: "PATCH", - url: params.location, - data: task - }); - return transformFunction(response, context, params); - }; +export async function onResponse(response: Response): Promise { + await ensureSuccessResponse(response); } /** @@ -143,7 +112,36 @@ export function _updateTaskFactory( * * @category Task */ -/* istanbul ignore next */ -export function updateTask(context: DvelopContext, params: UpdateTaskParams): Promise { - return _updateTaskFactory(_defaultHttpRequestFunction, () => { })(context, params); +export async function updateTask(context: DvelopContext, params: UpdateTaskParams): Promise; +export async function updateTask(context: DvelopContext, params: UpdateTaskParams, options: DvelopOptions): Promise; +export async function updateTask( + context: DvelopContext, + params: UpdateTaskParams, + options: DvelopOptions = { + onResponse: onResponse + } +): Promise { + + const task: any = { ...params }; + delete task.location; + + if (params.dueDate) { + task.dueDate = params.dueDate.toISOString(); + } + + if (params.reminderDate) { + task.reminderDate = params.reminderDate.toISOString(); + } + + if (params.dmsObject) { + task.dmsReferences = [{ + repoId: params.dmsObject.repositoryId, + objectId: params.dmsObject.dmsObjectId + }]; + } + + return dvelopFetch(context, params.location, { + method: "PATCH", + body: JSON.stringify(task) + }, options); } \ No newline at end of file diff --git a/packages/task/src/utils/http.spec.ts b/packages/task/src/utils/http.spec.ts deleted file mode 100644 index 3047855a..00000000 --- a/packages/task/src/utils/http.spec.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { BadInputError, DvelopContext, DvelopHttpClient, DvelopHttpError, DvelopHttpRequestConfig, DvelopHttpResponse, DvelopSdkError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; -import { _defaultHttpRequestFunctionFactory, TaskError, InvalidTaskDefinitionError } from "./http"; - -describe("defaultHttpRequestFunctionFactory", () => { - - let mockRequestFunction = jest.fn(); - let mockHttpClient: DvelopHttpClient = { - request: mockRequestFunction - }; - - let context: DvelopContext; - let config: DvelopHttpRequestConfig; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it("should call request", async () => { - - const response: DvelopHttpResponse = { - data: "HiItsMeResponse" - } as DvelopHttpResponse; - mockRequestFunction.mockResolvedValue(response); - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - const result = await requestFunction(context, config); - - expect(mockRequestFunction).toHaveBeenCalledTimes(1); - expect(result).toBe(response); - }); - - describe("error-handling", () => { - - describe("on statusCode 400", () => { - - it("should throw InvalidTaskDefinitionError on errorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 400, - data: { - "HiItsMeValidation": "HiItsMeValidationError" - } - } as DvelopHttpResponse, - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: InvalidTaskDefinitionError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof InvalidTaskDefinitionError).toBeTruthy(); - expect(expectedError.message).toEqual("Taskdefinition is invalid. See 'validation'-property for more information."); - expect(expectedError.validation).toEqual(error.response.data); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic BadInputError on missing errorDto", async () => { - - const error: DvelopHttpError = { - response: { - status: 400 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof BadInputError).toBeTruthy(); - expect(expectedError.message).toEqual("Task-App responded with Status 400 indicating bad Request-Parameters. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - }); - - describe("on statusCode 401", () => { - - it("should throw Unauthorized on body", async () => { - - const error: DvelopHttpError = { - response: { - status: 401, - data: "HiItsMeErrorReason" - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: UnauthorizedError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof DvelopSdkError).toBeTruthy(); - expect(expectedError.message).toEqual((error.response.data as any)); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic UnauthorizedError on missing Body", async () => { - - const error: DvelopHttpError = { - response: { - status: 401 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof UnauthorizedError).toBeTruthy(); - expect(expectedError.message).toEqual("Task-App responded with Status 401 indicating bad authSessionId."); - expect(expectedError.originalError).toBe(error); - }); - }); - - it("should throw generic ForbiddenError on statusCode 403", async () => { - - const error: DvelopHttpError = { - response: { - status: 403 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof ForbiddenError).toBeTruthy(); - expect(expectedError.message).toEqual("Task-App responded with Status 403 indicating a forbidden action. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic NotFoundError on statusCode 404", async () => { - - const error: DvelopHttpError = { - response: { - status: 404 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof NotFoundError).toBeTruthy(); - expect(expectedError.message).toEqual("Task-App responded with Status 404 indicating a requested resource does not exist. See 'originalError'-property for details."); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic TaskError on unknown status code", async () => { - - const error: DvelopHttpError = { - response: { - status: 500 - } as DvelopHttpResponse, - message: "HiItsMeHttpError" - } as DvelopHttpError; - - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof TaskError).toBeTruthy(); - expect(expectedError.message).toEqual(`Task-App responded with status ${error.response.status}. See 'originalError'-property for details.`); - expect(expectedError.originalError).toBe(error); - }); - - it("should throw generic TaskError on no response", async () => { - const error = new Error("HiItsMeError"); - mockRequestFunction.mockRejectedValue(error); - - - const requestFunction = _defaultHttpRequestFunctionFactory(mockHttpClient); - let expectedError: DvelopSdkError; - try { - await requestFunction(context, config); - } catch (e: any) { - expectedError = e; - } - - expect(expectedError instanceof TaskError).toBeTruthy(); - expect(expectedError.message).toEqual(`Request to Task-App failed: ${error.message}. See 'originalError'-property for details.`); - expect(expectedError.originalError).toBe(error); - }); - }); -}); \ No newline at end of file diff --git a/packages/task/src/utils/http.ts b/packages/task/src/utils/http.ts deleted file mode 100644 index fad965d7..00000000 --- a/packages/task/src/utils/http.ts +++ /dev/null @@ -1,106 +0,0 @@ - -import { DvelopContext, DvelopHttpRequestConfig, DvelopHttpResponse, DvelopHttpClient, defaultDvelopHttpClientFactory, BadInputError, UnauthorizedError, ForbiddenError, NotFoundError, DvelopSdkError } from "@dvelop-sdk/core"; -export { DvelopHttpRequestConfig as HttpConfig, DvelopHttpResponse as HttpResponse } from "@dvelop-sdk/core"; - - -/** -* Generic Error for task-package. -* @category Error -*/ -/* istanbul ignore next */ -export class TaskError extends DvelopSdkError { - // eslint-disable-next-line no-unused-vars - constructor(public message: string, public originalError?: Error) { - super(message); - Object.setPrototypeOf(this, TaskError.prototype); - } -} - -/** -* Validation for task -* @category Error -*/ -export interface TaskValidation { - invalidTaskDefinition: boolean; - missingSubject: boolean; - invalidSubject: boolean; - invalidDescription: boolean; - missingAssignees: boolean; - invalidSender: boolean; - invalidAssigneeIDs: string[]; - invalidDueDate: boolean; - invalidPriority: boolean; - invalidReminderDate: boolean; - invalidRetentionTime: boolean; - invalidHrefs: string[]; - invalidCorrelationKey: boolean; - missingCorrelationKey: boolean; - invalidContext: boolean; - invalidMetadata: boolean; - invalidOptions: string[]; - invalidDmsReferences: boolean; -} - -/** - * Indicates an invalid task-definition. See ```validation```-property for more information. - * @category Error - */ -export class InvalidTaskDefinitionError extends BadInputError { - // eslint-disable-next-line no-unused-vars - constructor(public validation: TaskValidation, public originalError?: Error) { - super("Taskdefinition is invalid. See 'validation'-property for more information.", originalError); - Object.setPrototypeOf(this, InvalidTaskDefinitionError.prototype); - } -} - -/** - * Factory used to create the default httpRequestFunction. See [Advanced Topics](https://github.com/d-velop/dvelop-sdk-node#advanced-topics) for more information. - * @internal - * @category Http - */ -export function _defaultHttpRequestFunctionFactory(httpClient: DvelopHttpClient): (context: DvelopContext, config: DvelopHttpRequestConfig) => Promise { - return async (context: DvelopContext, config: DvelopHttpRequestConfig) => { - - try { - return await httpClient.request(context, config); - } catch (error: any) { - - if (error.response) { - - switch (error.response.status) { - case 400: - if (error.response.data) { - throw new InvalidTaskDefinitionError(error.response.data, error); - } else { - throw new BadInputError("Task-App responded with Status 400 indicating bad Request-Parameters. See 'originalError'-property for details.", error); - } - - case 401: - throw new UnauthorizedError(error.response.data || "Task-App responded with Status 401 indicating bad authSessionId.", error); - - case 403: - throw new ForbiddenError("Task-App responded with Status 403 indicating a forbidden action. See 'originalError'-property for details.", error); - - case 404: - throw new NotFoundError("Task-App responded with Status 404 indicating a requested resource does not exist. See 'originalError'-property for details.", error); - case 429: - throw new TaskError("Task-App responded with status 429 indicating that you sent too many requests in a short time. Consider throttling your requests.", error); - default: - throw new TaskError(`Task-App responded with status ${error.response.status}. See 'originalError'-property for details.`, error); - } - } else { - throw new TaskError(`Request to Task-App failed: ${error.message}. See 'originalError'-property for details.`, error); - } - } - }; -} - -/** - * Default httpRequestFunction used in task-package. - * @internal - * @category Http - */ -/* istanbul ignore next */ -export async function _defaultHttpRequestFunction(context: DvelopContext, config: DvelopHttpRequestConfig): Promise { - return _defaultHttpRequestFunctionFactory(defaultDvelopHttpClientFactory())(context, config); -} \ No newline at end of file diff --git a/packages/task/src/utils/task-error.spec.ts b/packages/task/src/utils/task-error.spec.ts new file mode 100644 index 00000000..5153cbd7 --- /dev/null +++ b/packages/task/src/utils/task-error.spec.ts @@ -0,0 +1,83 @@ +import { BadInputError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; +import { InvalidTaskDefinitionError, TaskError, ensureSuccessResponse } from "./task-error"; + +describe("ensureSuccessResponse", () => { + + it("should not throw on 200", async () => { + const response = new Response("anything", { status: 200 }); + await expect(ensureSuccessResponse(response)).resolves.toBeUndefined(); + }); + + it("should not throw on 204", async () => { + const response = new Response(null, { status: 204 }); + await expect(ensureSuccessResponse(response)).resolves.toBeUndefined(); + }); + + describe("on statusCode 400", () => { + + it("should throw InvalidTaskDefinitionError with validation from body", async () => { + const validation = { "HiItsMeValidation": "HiItsMeValidationError" }; + const response = new Response(JSON.stringify(validation), { + status: 400, + headers: { "Content-Type": "application/json" } + }); + + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(InvalidTaskDefinitionError); + expect(err.message).toEqual("Taskdefinition is invalid. See 'validation'-property for more information."); + expect(err.validation).toEqual(validation); + }); + + it("should throw generic BadInputError on missing body", async () => { + const response = new Response(null, { status: 400 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(BadInputError); + expect(err.message).toEqual("Task-App responded with Status 400 indicating bad Request-Parameters."); + }); + }); + + describe("on statusCode 401", () => { + + it("should throw UnauthorizedError with string body", async () => { + const response = new Response("HiItsMeErrorReason", { status: 401 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(UnauthorizedError); + expect(err.message).toEqual("HiItsMeErrorReason"); + }); + + it("should throw generic UnauthorizedError on missing body", async () => { + const response = new Response(null, { status: 401 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(UnauthorizedError); + expect(err.message).toEqual("Task-App responded with Status 401 indicating bad authSessionId."); + }); + }); + + it("should throw generic ForbiddenError on statusCode 403", async () => { + const response = new Response(null, { status: 403 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(ForbiddenError); + expect(err.message).toEqual("Task-App responded with Status 403 indicating a forbidden action."); + }); + + it("should throw generic NotFoundError on statusCode 404", async () => { + const response = new Response(null, { status: 404 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(NotFoundError); + expect(err.message).toEqual("Task-App responded with Status 404 indicating a requested resource does not exist."); + }); + + it("should throw TaskError on statusCode 429", async () => { + const response = new Response(null, { status: 429 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(TaskError); + expect(err.message).toEqual("Task-App responded with status 429 indicating that you sent too many requests in a short time. Consider throttling your requests."); + }); + + it("should throw generic TaskError on unknown status code", async () => { + const response = new Response(null, { status: 500 }); + const err: any = await ensureSuccessResponse(response).catch((e: any) => e); + expect(err).toBeInstanceOf(TaskError); + expect(err.message).toEqual("Task-App responded with status 500."); + }); +}); diff --git a/packages/task/src/utils/task-error.ts b/packages/task/src/utils/task-error.ts new file mode 100644 index 00000000..23be8207 --- /dev/null +++ b/packages/task/src/utils/task-error.ts @@ -0,0 +1,95 @@ +import { BadInputError, DvelopSdkError, ForbiddenError, NotFoundError, UnauthorizedError } from "@dvelop-sdk/core"; + +/** + * Generic Error for task-package. + * @category Error + */ +/* istanbul ignore next */ +export class TaskError extends DvelopSdkError { + + constructor(public message: string, public originalError?: Error) { + super(message); + Object.setPrototypeOf(this, TaskError.prototype); + } +} + +/** + * Validation for task + * @category Error + */ +export interface TaskValidation { + invalidTaskDefinition: boolean; + missingSubject: boolean; + invalidSubject: boolean; + invalidDescription: boolean; + missingAssignees: boolean; + invalidSender: boolean; + invalidAssigneeIDs: string[]; + invalidDueDate: boolean; + invalidPriority: boolean; + invalidReminderDate: boolean; + invalidRetentionTime: boolean; + invalidHrefs: string[]; + invalidCorrelationKey: boolean; + missingCorrelationKey: boolean; + invalidContext: boolean; + invalidMetadata: boolean; + invalidOptions: string[]; + invalidDmsReferences: boolean; +} + +/** + * Indicates an invalid task-definition. See ```validation```-property for more information. + * @category Error + */ +export class InvalidTaskDefinitionError extends BadInputError { + + constructor(public validation: TaskValidation, public originalError?: Error) { + super("Taskdefinition is invalid. See 'validation'-property for more information.", originalError); + Object.setPrototypeOf(this, InvalidTaskDefinitionError.prototype); + } +} + +/** + * Throws a typed error if the response indicates failure. + * @internal + * @category Http + */ +export async function ensureSuccessResponse(response: Response): Promise { + + if (response.ok) return; + + let body: any; + try { + body = await response.clone().json(); + } catch { + try { + body = await response.clone().text(); + } catch { + body = undefined; + } + } + + let reason: string | undefined; + if (body) { + reason = typeof body === "string" ? body : (body.reason ?? undefined); + } + + switch (response.status) { + case 400: + if (body && typeof body === "object") { + throw new InvalidTaskDefinitionError(body); + } + throw new BadInputError("Task-App responded with Status 400 indicating bad Request-Parameters."); + case 401: + throw new UnauthorizedError(reason ?? "Task-App responded with Status 401 indicating bad authSessionId."); + case 403: + throw new ForbiddenError("Task-App responded with Status 403 indicating a forbidden action."); + case 404: + throw new NotFoundError("Task-App responded with Status 404 indicating a requested resource does not exist."); + case 429: + throw new TaskError("Task-App responded with status 429 indicating that you sent too many requests in a short time. Consider throttling your requests."); + default: + throw new TaskError(`Task-App responded with status ${response.status}.`); + } +} diff --git a/packages/task/tsconfig.build.json b/packages/task/tsconfig.build.json new file mode 100644 index 00000000..0c9b67d5 --- /dev/null +++ b/packages/task/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig-base.json", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.spec.ts", "src/**/tmp-*.ts"] +} diff --git a/packages/task/tsconfig.json b/packages/task/tsconfig.json index 5ad7fa35..3912f472 100644 --- a/packages/task/tsconfig.json +++ b/packages/task/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "../../tsconfig-base.json", "compilerOptions": { - "outDir": "lib", /* Redirect output structure to the directory. */ - "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "noEmit": true, + "composite": false, + "declaration": false, + "declarationMap": false }, - "include": ["src/**/*"], - "exclude": ["src/**/*.spec.ts"], -} \ No newline at end of file + "include": ["src/**/*"] +} diff --git a/tsconfig-base.json b/tsconfig-base.json index 808ea662..d1c96910 100644 --- a/tsconfig-base.json +++ b/tsconfig-base.json @@ -2,14 +2,15 @@ /* Common compiler options for all modules */ "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ - "target": "ES5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ - "module": "CommonJS", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - "moduleResolution": "node", + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", "declaration": true, /* Generates corresponding '.d.ts' file. */ "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ "composite": true, /* Enable project compilation cf. https://www.typescriptlang.org/docs/handbook/project-references.html#composite */ "sourceMap": true, /* Generates corresponding '.map' file. */ "strict": true, /* Enable all strict type-checking options. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "types": ["node", "jest", "express"] /* Pin ambient type packages so editors/IDEs include them in every package. */ } } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 4a32c710..811938ba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,32 +1,16 @@ { - /* References to all modules so that the whole repo can be compiled with tsc -b + /* References to all build configs so that the whole repo can be compiled with tsc -b cf. https://www.typescriptlang.org/docs/handbook/project-references.html#overall-structure */ "files": [], "include": [], "references": [ - { - "path": "./packages/core", - }, - { - "path": "./packages/app-router", - }, - { - "path": "./packages/identityprovider" - }, - { - "path": "./packages/task" - }, - { - "path": "./packages/dms" - }, - { - "path": "./packages/business-objects" - }, - { - "path": "./packages/express-utils" - }, - { - "path": "./packages/logging" - } + { "path": "./packages/core/tsconfig.build.json" }, + { "path": "./packages/app-router/tsconfig.build.json" }, + { "path": "./packages/identityprovider/tsconfig.build.json" }, + { "path": "./packages/task/tsconfig.build.json" }, + { "path": "./packages/dms/tsconfig.build.json" }, + { "path": "./packages/business-objects/tsconfig.build.json" }, + { "path": "./packages/express-utils/tsconfig.build.json" }, + { "path": "./packages/logging/tsconfig.build.json" } ] }