id-codec.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { ID } from '@vendure/common/shared-types';
  2. import { EntityIdStrategy } from '../../config/entity-id-strategy/entity-id-strategy';
  3. import { VendureEntity } from '../../entity/base/base.entity';
  4. /**
  5. * This service is responsible for encoding/decoding entity IDs according to the configured EntityIdStrategy.
  6. * It should only need to be used in resolvers - the design is that once a request hits the business logic layer
  7. * (ProductService etc) all entity IDs are in the form used as the primary key in the database.
  8. */
  9. export class IdCodec {
  10. constructor(private entityIdStrategy: EntityIdStrategy) {}
  11. /**
  12. * Decode an id from the client into the format used as the database primary key.
  13. * Acts recursively on all objects containing an "id" property.
  14. *
  15. * @param target - The object to be decoded
  16. * @param transformKeys - An optional array of keys of the target to be decoded. If not defined,
  17. * then the default recursive behaviour will be used.
  18. * @return A decoded clone of the target
  19. */
  20. decode<T extends string | number | object | undefined>(target: T, transformKeys?: string[]): T {
  21. const transformKeysWithId = [...(transformKeys || []), 'id'];
  22. return this.transformRecursive(
  23. target,
  24. input => this.entityIdStrategy.decodeId(input),
  25. transformKeysWithId,
  26. );
  27. }
  28. /**
  29. * Encode any entity ids according to the encode.
  30. * Acts recursively on all objects containing an "id" property.
  31. *
  32. * @param target - The object to be encoded
  33. * @param transformKeys - An optional array of keys of the target to be encoded. If not defined,
  34. * then the default recursive behaviour will be used.
  35. * @return An encoded clone of the target
  36. */
  37. encode<T extends string | number | boolean | object | undefined>(target: T, transformKeys?: string[]): T {
  38. const transformKeysWithId = [...(transformKeys || []), 'id'];
  39. return this.transformRecursive(
  40. target,
  41. input => this.entityIdStrategy.encodeId(input),
  42. transformKeysWithId,
  43. );
  44. }
  45. private transformRecursive<T>(target: T, transformFn: (input: any) => ID, transformKeys?: string[]): T {
  46. // noinspection SuspiciousInstanceOfGuard
  47. if (
  48. target == null ||
  49. typeof target === 'boolean' ||
  50. target instanceof Promise ||
  51. target instanceof Date ||
  52. target instanceof RegExp
  53. ) {
  54. return target;
  55. }
  56. if (typeof target === 'string' || typeof target === 'number') {
  57. return transformFn(target as any) as any;
  58. }
  59. if (Array.isArray(target)) {
  60. (target as any) = target.slice(0);
  61. if (target.length === 0 || typeof target[0] === 'string' || typeof target[0] === 'number') {
  62. return target;
  63. }
  64. const isSimpleObject = this.isSimpleObject(target[0]);
  65. if (isSimpleObject) {
  66. const length = target.length;
  67. for (let i = 0; i < length; i++) {
  68. target[i] = this.transform(target[i], transformFn, transformKeys);
  69. }
  70. } else {
  71. const length = target.length;
  72. for (let i = 0; i < length; i++) {
  73. target[i] = this.transformRecursive(target[i], transformFn, transformKeys);
  74. }
  75. }
  76. } else {
  77. target = this.transform(target, transformFn, transformKeys);
  78. for (const key of Object.keys(target)) {
  79. if (this.isObject(target[key])) {
  80. target[key] = this.transformRecursive(target[key], transformFn, transformKeys);
  81. }
  82. }
  83. }
  84. return target;
  85. }
  86. private transform<T>(target: T, transformFn: (input: any) => ID, transformKeys?: string[]): T {
  87. const clone = Object.assign({}, target);
  88. if (transformKeys) {
  89. for (const key of transformKeys) {
  90. if (target[key]) {
  91. const val = target[key];
  92. if (Array.isArray(val)) {
  93. clone[key] = val.map(v => transformFn(v));
  94. } else {
  95. clone[key] = transformFn(val);
  96. }
  97. }
  98. }
  99. }
  100. return clone;
  101. }
  102. private isSimpleObject(target: any): boolean {
  103. const values = Object.values(target);
  104. for (const value of values) {
  105. if (this.isObject(value)) {
  106. return false;
  107. }
  108. }
  109. return true;
  110. }
  111. private isObject(target: any): target is object {
  112. return typeof target === 'object' && target != null;
  113. }
  114. }