1
0

id-codec.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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<any>) {}
  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 (
  63. target.length === 0 ||
  64. typeof target[0] === 'string' ||
  65. typeof target[0] === 'number' ||
  66. typeof target[0] === 'boolean' ||
  67. target[0] == null
  68. ) {
  69. return target;
  70. }
  71. const isSimpleObject = this.isSimpleObject(target[0]);
  72. if (isSimpleObject) {
  73. const length = target.length;
  74. for (let i = 0; i < length; i++) {
  75. target[i] = this.transform(target[i], transformFn, transformKeys);
  76. }
  77. } else {
  78. const length = target.length;
  79. for (let i = 0; i < length; i++) {
  80. target[i] = this.transformRecursive(target[i], transformFn, transformKeys);
  81. }
  82. }
  83. } else {
  84. target = this.transform(target, transformFn, transformKeys);
  85. for (const key of Object.keys(target)) {
  86. if (this.isObject(target[key as keyof T])) {
  87. target[key as keyof T] = this.transformRecursive(
  88. target[key as keyof T],
  89. transformFn,
  90. transformKeys,
  91. );
  92. }
  93. }
  94. }
  95. return target;
  96. }
  97. private transform<T>(target: T, transformFn: (input: any) => ID, transformKeys?: string[]): T {
  98. if (target == null || !this.isObject(target) || Array.isArray(target)) {
  99. return target;
  100. }
  101. const clone = Object.assign({}, target);
  102. if (transformKeys) {
  103. for (const key of transformKeys) {
  104. if (target[key as keyof T]) {
  105. const val = target[key as keyof T];
  106. if (Array.isArray(val)) {
  107. (clone as any)[key] = val.map(v => transformFn(v));
  108. } else {
  109. (clone as any)[key] = transformFn(val);
  110. }
  111. }
  112. }
  113. }
  114. return clone;
  115. }
  116. private isSimpleObject(target: any): boolean {
  117. if (!target) {
  118. return true;
  119. }
  120. const values = Object.values(target);
  121. for (const value of values) {
  122. if (this.isObject(value) || value === null) {
  123. return false;
  124. }
  125. }
  126. return true;
  127. }
  128. private isObject(target: any): target is object {
  129. return typeof target === 'object' && target != null;
  130. }
  131. }