diff --git a/.gitignore b/.gitignore index 7d12b180..a255daae 100644 --- a/.gitignore +++ b/.gitignore @@ -111,7 +111,7 @@ typings/ # dotenv environment variables file .env .env.* -test/env/ +test/env/*.* # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e72e1736..9d63cda2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -208,6 +208,21 @@ packages: - supports-color dev: true + /@babel/helper-define-polyfill-provider@0.6.0(@babel/core@7.24.0): + resolution: {integrity: sha512-efwOM90nCG6YeT8o3PCyBVSxRfmILxCNL+TNI8CGQl7a62M0Wd9VkV+XHwIlkOz1r4b+lxu6gBjdWiOMdUCrCQ==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-plugin-utils': 7.24.0 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/helper-environment-visitor@7.22.20: resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} engines: {node: '>=6.9.0'} @@ -1232,7 +1247,7 @@ packages: '@babel/plugin-transform-unicode-regex': 7.23.3(@babel/core@7.24.0) '@babel/plugin-transform-unicode-sets-regex': 7.23.3(@babel/core@7.24.0) '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.0) - babel-plugin-polyfill-corejs2: 0.4.8(@babel/core@7.24.0) + babel-plugin-polyfill-corejs2: 0.4.9(@babel/core@7.24.0) babel-plugin-polyfill-corejs3: 0.9.0(@babel/core@7.24.0) babel-plugin-polyfill-regenerator: 0.5.5(@babel/core@7.24.0) core-js-compat: 3.36.0 @@ -2146,7 +2161,7 @@ packages: fs-extra: 11.2.0 lodash-es: 4.17.21 nerf-dart: 1.0.0 - normalize-url: 8.0.0 + normalize-url: 8.0.1 npm: 10.5.0 rc: 1.2.8 read-pkg: 9.0.1 @@ -2350,7 +2365,7 @@ packages: ignore: 5.3.1 natural-compare: 1.4.0 semver: 7.6.0 - ts-api-utils: 1.2.1(typescript@5.4.2) + ts-api-utils: 1.3.0(typescript@5.4.2) typescript: 5.4.2 transitivePeerDependencies: - supports-color @@ -2399,7 +2414,7 @@ packages: '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.4.2) debug: 4.3.4 eslint: 8.57.0 - ts-api-utils: 1.2.1(typescript@5.4.2) + ts-api-utils: 1.3.0(typescript@5.4.2) typescript: 5.4.2 transitivePeerDependencies: - supports-color @@ -2426,7 +2441,7 @@ packages: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.6.0 - ts-api-utils: 1.2.1(typescript@5.4.2) + ts-api-utils: 1.3.0(typescript@5.4.2) typescript: 5.4.2 transitivePeerDependencies: - supports-color @@ -2743,14 +2758,14 @@ packages: '@types/babel__traverse': 7.20.5 dev: true - /babel-plugin-polyfill-corejs2@0.4.8(@babel/core@7.24.0): - resolution: {integrity: sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==} + /babel-plugin-polyfill-corejs2@0.4.9(@babel/core@7.24.0): + resolution: {integrity: sha512-BXIWIaO3MewbXWdJdIGDWZurv5OGJlFNo7oy20DpB3kWDVJLcY2NRypRsRUbRe5KMqSNLuOGnWTFQQtY5MAsRw==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 dependencies: '@babel/compat-data': 7.23.5 '@babel/core': 7.24.0 - '@babel/helper-define-polyfill-provider': 0.5.0(@babel/core@7.24.0) + '@babel/helper-define-polyfill-provider': 0.6.0(@babel/core@7.24.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -2847,8 +2862,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001596 - electron-to-chromium: 1.4.695 + caniuse-lite: 1.0.30001597 + electron-to-chromium: 1.4.699 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) dev: true @@ -2882,7 +2897,7 @@ packages: es-errors: 1.3.0 function-bind: 1.1.2 get-intrinsic: 1.2.4 - set-function-length: 1.2.1 + set-function-length: 1.2.2 dev: true /callsites@3.1.0: @@ -2900,8 +2915,8 @@ packages: engines: {node: '>=10'} dev: true - /caniuse-lite@1.0.30001596: - resolution: {integrity: sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==} + /caniuse-lite@1.0.30001597: + resolution: {integrity: sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==} dev: true /chalk@2.4.2: @@ -3291,8 +3306,8 @@ packages: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true - /electron-to-chromium@1.4.695: - resolution: {integrity: sha512-eMijZmeqPtm774pCZIOrfUHMs/7ls++W1sLhxwqgu8KQ8E2WmMtzwyqOMt0XXUJ3HTIPfuwlfwF+I5cwnfItBA==} + /electron-to-chromium@1.4.699: + resolution: {integrity: sha512-I7q3BbQi6e4tJJN5CRcyvxhK0iJb34TV8eJQcgh+fR2fQ8miMgZcEInckCo1U9exDHbfz7DLDnFn8oqH/VcRKw==} dev: true /emittery@0.13.1: @@ -3351,7 +3366,7 @@ packages: has-property-descriptors: 1.0.2 has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.1 + hasown: 2.0.2 internal-slot: 1.0.7 is-array-buffer: 3.0.4 is-callable: 1.2.7 @@ -3365,7 +3380,7 @@ packages: object-keys: 1.1.1 object.assign: 4.1.5 regexp.prototype.flags: 1.5.2 - safe-array-concat: 1.1.0 + safe-array-concat: 1.1.2 safe-regex-test: 1.0.3 string.prototype.trim: 1.2.8 string.prototype.trimend: 1.0.7 @@ -3375,7 +3390,7 @@ packages: typed-array-byte-offset: 1.0.2 typed-array-length: 1.0.5 unbox-primitive: 1.0.2 - which-typed-array: 1.1.14 + which-typed-array: 1.1.15 dev: true /es-array-method-boxes-properly@1.0.0: @@ -3400,13 +3415,13 @@ packages: dependencies: get-intrinsic: 1.2.4 has-tostringtag: 1.0.2 - hasown: 2.0.1 + hasown: 2.0.2 dev: true /es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} dependencies: - hasown: 2.0.1 + hasown: 2.0.2 dev: true /es-to-primitive@1.2.1: @@ -3599,7 +3614,7 @@ packages: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) - hasown: 2.0.1 + hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 minimatch: 3.1.2 @@ -4021,7 +4036,7 @@ packages: function-bind: 1.1.2 has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.1 + hasown: 2.0.2 dev: true /get-package-type@0.1.0: @@ -4218,8 +4233,8 @@ packages: has-symbols: 1.0.3 dev: true - /hasown@2.0.1: - resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==} + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 @@ -4355,7 +4370,7 @@ packages: engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 - hasown: 2.0.1 + hasown: 2.0.2 side-channel: 1.0.6 dev: true @@ -4408,7 +4423,7 @@ packages: /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: - hasown: 2.0.1 + hasown: 2.0.2 dev: true /is-date-object@1.0.5: @@ -4517,7 +4532,7 @@ packages: resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} dependencies: - which-typed-array: 1.1.14 + which-typed-array: 1.1.15 dev: true /is-unicode-supported@2.0.0: @@ -5426,8 +5441,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /normalize-url@8.0.0: - resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==} + /normalize-url@8.0.1: + resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} engines: {node: '>=14.16'} dev: true @@ -5721,7 +5736,7 @@ packages: dependencies: '@babel/code-frame': 7.23.5 index-to-position: 0.1.2 - type-fest: 4.11.1 + type-fest: 4.12.0 dev: true /parse5-htmlparser2-tree-adapter@6.0.1: @@ -5914,7 +5929,7 @@ packages: dependencies: find-up-simple: 1.0.0 read-pkg: 9.0.1 - type-fest: 4.11.1 + type-fest: 4.12.0 dev: true /read-pkg@9.0.1: @@ -5924,7 +5939,7 @@ packages: '@types/normalize-package-data': 2.4.4 normalize-package-data: 6.0.0 parse-json: 8.1.0 - type-fest: 4.11.1 + type-fest: 4.12.0 unicorn-magic: 0.1.0 dev: true @@ -6055,8 +6070,8 @@ packages: queue-microtask: 1.2.3 dev: true - /safe-array-concat@1.1.0: - resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} + /safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} dependencies: call-bind: 1.0.7 @@ -6142,8 +6157,8 @@ packages: lru-cache: 6.0.0 dev: true - /set-function-length@1.2.1: - resolution: {integrity: sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==} + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 @@ -6513,8 +6528,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /ts-api-utils@1.2.1(typescript@5.4.2): - resolution: {integrity: sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==} + /ts-api-utils@1.3.0(typescript@5.4.2): + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' @@ -6603,8 +6618,8 @@ packages: engines: {node: '>=14.16'} dev: true - /type-fest@4.11.1: - resolution: {integrity: sha512-MFMf6VkEVZAETidGGSYW2B1MjXbGX+sWIywn2QPEaJ3j08V+MwVRHMXtf2noB8ENJaD0LIun9wh5Z6OPNf1QzQ==} + /type-fest@4.12.0: + resolution: {integrity: sha512-5Y2/pp2wtJk8o08G0CMkuFPCO354FGwk/vbidxrdhRGZfd0tFnb4Qb8anp9XxXriwBgVPjdWbKpGl4J9lJY2jQ==} engines: {node: '>=16'} dev: true @@ -6790,8 +6805,8 @@ packages: is-symbol: 1.0.4 dev: true - /which-typed-array@1.1.14: - resolution: {integrity: sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==} + /which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 diff --git a/src/config.ts b/src/config.ts index 63435828..b4a70be7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -11,6 +11,9 @@ const config = { client: { timeout: 15000, requiredAttributes: ['organization', 'accessToken'], + }, + jsonapi: { + maxResourceIncluded: 2 } } as const diff --git a/src/jsonapi.ts b/src/jsonapi.ts index 73353ff7..afaf01f6 100644 --- a/src/jsonapi.ts +++ b/src/jsonapi.ts @@ -3,6 +3,7 @@ import type { Value as JSONValue } from 'json-typescript' import type { DocWithData, Included, ResourceIdentifierObject, ResourceObject as JSONAPIObject, AttributesObject, RelationshipsObject } from 'jsonapi-typescript' import type { ResourceCreate, ResourceUpdate, ResourceId, ResourceType, Resource, ResourceRel } from './resource' import { isResourceId, isResourceType } from './common' +import config from './config' import Debug from './debug' const debug = Debug('jsonapi') @@ -41,7 +42,7 @@ const findIncluded = (rel: ResourceIdentifierObject, included: Included = []): J } -const denormalizeResource = (res: any, included?: Included): T => { +const denormalizeResource = (res: any, included?: Included, chain: ResourceIdentifierObject[] = []): T => { debug('denormalize resource: %O, %o', res, included || {}) @@ -54,13 +55,16 @@ const denormalizeResource = (res: any, included?: Includ } if (res.relationships) Object.keys(res.relationships as object).forEach(key => { - const rel = res.relationships[key].data as ResourceIdentifierObject + const rel: ResourceIdentifierObject = res.relationships[key].data if (rel) { - if (Array.isArray(rel)) resource[key] = rel.map((r: ResourceIdentifierObject) => denormalizeResource(findIncluded(r, included), included)) - else resource[key] = denormalizeResource(findIncluded(rel, included), included) + if (chain.filter(r => (r.id === rel.id) && (r.type === rel.type)).length >= config.jsonapi.maxResourceIncluded) resource[key] = rel + else { + if (Array.isArray(rel)) resource[key] = rel.map((r: ResourceIdentifierObject) => denormalizeResource(findIncluded(r, included), included, [...chain, r])) + else resource[key] = denormalizeResource(findIncluded(rel, included), included, [...chain, rel]) + } } else if (rel === null) resource[key] = null }) - + debug('denormalized resource: %O', resource) return resource