id-codec.ts 5.0 KB

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