serializable.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import { keyToJson, mapKeys } from "./map_keys.js";
  2. function shallowCopy(obj) {
  3. return Array.isArray(obj) ? [...obj] : { ...obj };
  4. }
  5. function replaceSecrets(root, secretsMap) {
  6. const result = shallowCopy(root);
  7. for (const [path, secretId] of Object.entries(secretsMap)) {
  8. const [last, ...partsReverse] = path.split(".").reverse();
  9. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  10. let current = result;
  11. for (const part of partsReverse.reverse()) {
  12. if (current[part] === undefined) {
  13. break;
  14. }
  15. current[part] = shallowCopy(current[part]);
  16. current = current[part];
  17. }
  18. if (current[last] !== undefined) {
  19. current[last] = {
  20. lc: 1,
  21. type: "secret",
  22. id: [secretId],
  23. };
  24. }
  25. }
  26. return result;
  27. }
  28. /**
  29. * Get a unique name for the module, rather than parent class implementations.
  30. * Should not be subclassed, subclass lc_name above instead.
  31. */
  32. export function get_lc_unique_name(
  33. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  34. serializableClass) {
  35. // "super" here would refer to the parent class of Serializable,
  36. // when we want the parent class of the module actually calling this method.
  37. const parentClass = Object.getPrototypeOf(serializableClass);
  38. const lcNameIsSubclassed = typeof serializableClass.lc_name === "function" &&
  39. (typeof parentClass.lc_name !== "function" ||
  40. serializableClass.lc_name() !== parentClass.lc_name());
  41. if (lcNameIsSubclassed) {
  42. return serializableClass.lc_name();
  43. }
  44. else {
  45. return serializableClass.name;
  46. }
  47. }
  48. export class Serializable {
  49. /**
  50. * The name of the serializable. Override to provide an alias or
  51. * to preserve the serialized module name in minified environments.
  52. *
  53. * Implemented as a static method to support loading logic.
  54. */
  55. static lc_name() {
  56. return this.name;
  57. }
  58. /**
  59. * The final serialized identifier for the module.
  60. */
  61. get lc_id() {
  62. return [
  63. ...this.lc_namespace,
  64. get_lc_unique_name(this.constructor),
  65. ];
  66. }
  67. /**
  68. * A map of secrets, which will be omitted from serialization.
  69. * Keys are paths to the secret in constructor args, e.g. "foo.bar.baz".
  70. * Values are the secret ids, which will be used when deserializing.
  71. */
  72. get lc_secrets() {
  73. return undefined;
  74. }
  75. /**
  76. * A map of additional attributes to merge with constructor args.
  77. * Keys are the attribute names, e.g. "foo".
  78. * Values are the attribute values, which will be serialized.
  79. * These attributes need to be accepted by the constructor as arguments.
  80. */
  81. get lc_attributes() {
  82. return undefined;
  83. }
  84. /**
  85. * A map of aliases for constructor args.
  86. * Keys are the attribute names, e.g. "foo".
  87. * Values are the alias that will replace the key in serialization.
  88. * This is used to eg. make argument names match Python.
  89. */
  90. get lc_aliases() {
  91. return undefined;
  92. }
  93. /**
  94. * A manual list of keys that should be serialized.
  95. * If not overridden, all fields passed into the constructor will be serialized.
  96. */
  97. get lc_serializable_keys() {
  98. return undefined;
  99. }
  100. constructor(kwargs, ..._args) {
  101. Object.defineProperty(this, "lc_serializable", {
  102. enumerable: true,
  103. configurable: true,
  104. writable: true,
  105. value: false
  106. });
  107. Object.defineProperty(this, "lc_kwargs", {
  108. enumerable: true,
  109. configurable: true,
  110. writable: true,
  111. value: void 0
  112. });
  113. if (this.lc_serializable_keys !== undefined) {
  114. this.lc_kwargs = Object.fromEntries(Object.entries(kwargs || {}).filter(([key]) => this.lc_serializable_keys?.includes(key)));
  115. }
  116. else {
  117. this.lc_kwargs = kwargs ?? {};
  118. }
  119. }
  120. toJSON() {
  121. if (!this.lc_serializable) {
  122. return this.toJSONNotImplemented();
  123. }
  124. if (
  125. // eslint-disable-next-line no-instanceof/no-instanceof
  126. this.lc_kwargs instanceof Serializable ||
  127. typeof this.lc_kwargs !== "object" ||
  128. Array.isArray(this.lc_kwargs)) {
  129. // We do not support serialization of classes with arg not a POJO
  130. // I'm aware the check above isn't as strict as it could be
  131. return this.toJSONNotImplemented();
  132. }
  133. const aliases = {};
  134. const secrets = {};
  135. const kwargs = Object.keys(this.lc_kwargs).reduce((acc, key) => {
  136. acc[key] = key in this ? this[key] : this.lc_kwargs[key];
  137. return acc;
  138. }, {});
  139. // get secrets, attributes and aliases from all superclasses
  140. for (
  141. // eslint-disable-next-line @typescript-eslint/no-this-alias
  142. let current = Object.getPrototypeOf(this); current; current = Object.getPrototypeOf(current)) {
  143. Object.assign(aliases, Reflect.get(current, "lc_aliases", this));
  144. Object.assign(secrets, Reflect.get(current, "lc_secrets", this));
  145. Object.assign(kwargs, Reflect.get(current, "lc_attributes", this));
  146. }
  147. // include all secrets used, even if not in kwargs,
  148. // will be replaced with sentinel value in replaceSecrets
  149. Object.keys(secrets).forEach((keyPath) => {
  150. // eslint-disable-next-line @typescript-eslint/no-this-alias, @typescript-eslint/no-explicit-any
  151. let read = this;
  152. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  153. let write = kwargs;
  154. const [last, ...partsReverse] = keyPath.split(".").reverse();
  155. for (const key of partsReverse.reverse()) {
  156. if (!(key in read) || read[key] === undefined)
  157. return;
  158. if (!(key in write) || write[key] === undefined) {
  159. if (typeof read[key] === "object" && read[key] != null) {
  160. write[key] = {};
  161. }
  162. else if (Array.isArray(read[key])) {
  163. write[key] = [];
  164. }
  165. }
  166. read = read[key];
  167. write = write[key];
  168. }
  169. if (last in read && read[last] !== undefined) {
  170. write[last] = write[last] || read[last];
  171. }
  172. });
  173. return {
  174. lc: 1,
  175. type: "constructor",
  176. id: this.lc_id,
  177. kwargs: mapKeys(Object.keys(secrets).length ? replaceSecrets(kwargs, secrets) : kwargs, keyToJson, aliases),
  178. };
  179. }
  180. toJSONNotImplemented() {
  181. return {
  182. lc: 1,
  183. type: "not_implemented",
  184. id: this.lc_id,
  185. };
  186. }
  187. }