@ezez/utils 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +3 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/omit.d.ts +1 -1
  7. package/dist/omit.d.ts.map +1 -1
  8. package/dist/omit.js.map +1 -1
  9. package/dist/replaceDeep.d.ts +5 -1
  10. package/dist/replaceDeep.d.ts.map +1 -1
  11. package/dist/replaceDeep.js +3 -20
  12. package/dist/replaceDeep.js.map +1 -1
  13. package/dist/replaceDeepByFn.d.ts +7 -0
  14. package/dist/replaceDeepByFn.d.ts.map +1 -0
  15. package/dist/replaceDeepByFn.js +47 -0
  16. package/dist/replaceDeepByFn.js.map +1 -0
  17. package/dist/serialize.d.ts.map +1 -1
  18. package/dist/serialize.js +56 -18
  19. package/dist/serialize.js.map +1 -1
  20. package/dist/trim.d.ts +3 -0
  21. package/dist/trim.d.ts.map +1 -0
  22. package/dist/trim.js +10 -0
  23. package/dist/trim.js.map +1 -0
  24. package/dist/trimEnd.d.ts +3 -0
  25. package/dist/trimEnd.d.ts.map +1 -0
  26. package/dist/trimEnd.js +12 -0
  27. package/dist/trimEnd.js.map +1 -0
  28. package/dist/trimStart.d.ts +3 -0
  29. package/dist/trimStart.d.ts.map +1 -0
  30. package/dist/trimStart.js +12 -0
  31. package/dist/trimStart.js.map +1 -0
  32. package/dist/utils/utils.d.ts +6 -0
  33. package/dist/utils/utils.d.ts.map +1 -0
  34. package/dist/utils/utils.js +10 -0
  35. package/dist/utils/utils.js.map +1 -0
  36. package/docs/assets/search.js +1 -1
  37. package/docs/functions/cap.html +8 -5
  38. package/docs/functions/capitalize.html +8 -5
  39. package/docs/functions/coalesce.html +8 -5
  40. package/docs/functions/compareArrays.html +8 -5
  41. package/docs/functions/compareProps.html +8 -5
  42. package/docs/functions/deserialize.html +8 -5
  43. package/docs/functions/ensureArray.html +8 -5
  44. package/docs/functions/ensureDate.html +8 -5
  45. package/docs/functions/ensureError.html +8 -5
  46. package/docs/functions/ensurePrefix.html +8 -5
  47. package/docs/functions/ensureSuffix.html +8 -5
  48. package/docs/functions/ensureTimestamp.html +8 -5
  49. package/docs/functions/escapeRegExp.html +8 -5
  50. package/docs/functions/formatDate.html +8 -5
  51. package/docs/functions/get.html +8 -5
  52. package/docs/functions/getMultiple.html +8 -5
  53. package/docs/functions/insertSeparator.html +8 -5
  54. package/docs/functions/isEmpty.html +8 -5
  55. package/docs/functions/isNumericString.html +8 -5
  56. package/docs/functions/isPlainObject.html +8 -5
  57. package/docs/functions/last.html +8 -5
  58. package/docs/functions/later-1.html +8 -5
  59. package/docs/functions/mapAsync.html +8 -5
  60. package/docs/functions/mapValues.html +8 -5
  61. package/docs/functions/match.html +8 -5
  62. package/docs/functions/merge.html +16 -13
  63. package/docs/functions/mostFrequent.html +8 -5
  64. package/docs/functions/noop.html +8 -5
  65. package/docs/functions/occurrences.html +8 -5
  66. package/docs/functions/omit.html +14 -7
  67. package/docs/functions/pick.html +9 -6
  68. package/docs/functions/pull.html +8 -5
  69. package/docs/functions/remove.html +8 -5
  70. package/docs/functions/removeCommonProperties.html +8 -5
  71. package/docs/functions/replace.html +8 -5
  72. package/docs/functions/replaceDeep.html +19 -7
  73. package/docs/functions/rethrow.html +8 -5
  74. package/docs/functions/round.html +8 -5
  75. package/docs/functions/safe.html +9 -6
  76. package/docs/functions/sample.html +8 -5
  77. package/docs/functions/samples.html +8 -5
  78. package/docs/functions/scale.html +8 -5
  79. package/docs/functions/seq.html +8 -5
  80. package/docs/functions/seqEarlyBreak.html +8 -5
  81. package/docs/functions/serialize.html +8 -5
  82. package/docs/functions/set.html +8 -5
  83. package/docs/functions/setImmutable.html +8 -5
  84. package/docs/functions/shuffle.html +8 -5
  85. package/docs/functions/sortBy.html +8 -5
  86. package/docs/functions/sortProps.html +8 -5
  87. package/docs/functions/stripPrefix.html +8 -5
  88. package/docs/functions/stripSuffix.html +8 -5
  89. package/docs/functions/throttle.html +8 -5
  90. package/docs/functions/toggle.html +8 -5
  91. package/docs/functions/trim.html +152 -0
  92. package/docs/functions/trimEnd.html +152 -0
  93. package/docs/functions/trimStart.html +152 -0
  94. package/docs/functions/truthy.html +8 -5
  95. package/docs/functions/unique.html +8 -5
  96. package/docs/functions/wait.html +8 -5
  97. package/docs/functions/waitFor.html +8 -5
  98. package/docs/functions/waitSync.html +8 -5
  99. package/docs/index.html +7 -4
  100. package/docs/interfaces/ComparePropsOptions.html +6 -6
  101. package/docs/interfaces/GetMultipleSource.html +8 -5
  102. package/docs/interfaces/GetSource.html +8 -5
  103. package/docs/interfaces/IsNumericStringOptions.html +9 -9
  104. package/docs/interfaces/OccurencesOptions.html +6 -6
  105. package/docs/interfaces/SetImmutableSource.html +8 -5
  106. package/docs/interfaces/SetSource.html +8 -5
  107. package/docs/interfaces/ThrottleOptions.html +7 -7
  108. package/docs/interfaces/ThrottledFunctionExtras.html +7 -7
  109. package/docs/modules.html +10 -4
  110. package/docs/pages/CHANGELOG.html +113 -64
  111. package/docs/pages/Introduction.html +7 -4
  112. package/docs/types/CustomDeserializers.html +8 -5
  113. package/docs/types/CustomSerializers.html +8 -5
  114. package/docs/types/Later.html +8 -5
  115. package/docs/types/MapValuesFn.html +8 -5
  116. package/docs/types/MatchCallback.html +8 -5
  117. package/docs/types/SeqEarlyBreaker.html +8 -5
  118. package/docs/types/SeqFn.html +8 -5
  119. package/docs/types/SeqFunctions.html +8 -5
  120. package/docs/types/SetImmutablePath.html +8 -5
  121. package/docs/types/ThrottledFunction.html +8 -5
  122. package/docs/variables/mapValuesUNSET.html +8 -5
  123. package/docs/variables/mergeUNSET.html +8 -5
  124. package/esm/index.d.ts +3 -0
  125. package/esm/index.d.ts.map +1 -1
  126. package/esm/index.js +3 -0
  127. package/esm/index.js.map +1 -1
  128. package/esm/omit.d.ts +1 -1
  129. package/esm/omit.d.ts.map +1 -1
  130. package/esm/omit.js.map +1 -1
  131. package/esm/replaceDeep.d.ts +5 -1
  132. package/esm/replaceDeep.d.ts.map +1 -1
  133. package/esm/replaceDeep.js +3 -20
  134. package/esm/replaceDeep.js.map +1 -1
  135. package/esm/replaceDeepByFn.d.ts +7 -0
  136. package/esm/replaceDeepByFn.d.ts.map +1 -0
  137. package/esm/replaceDeepByFn.js +44 -0
  138. package/esm/replaceDeepByFn.js.map +1 -0
  139. package/esm/serialize.d.ts.map +1 -1
  140. package/esm/serialize.js +56 -18
  141. package/esm/serialize.js.map +1 -1
  142. package/esm/trim.d.ts +3 -0
  143. package/esm/trim.d.ts.map +1 -0
  144. package/esm/trim.js +7 -0
  145. package/esm/trim.js.map +1 -0
  146. package/esm/trimEnd.d.ts +3 -0
  147. package/esm/trimEnd.d.ts.map +1 -0
  148. package/esm/trimEnd.js +9 -0
  149. package/esm/trimEnd.js.map +1 -0
  150. package/esm/trimStart.d.ts +3 -0
  151. package/esm/trimStart.d.ts.map +1 -0
  152. package/esm/trimStart.js +9 -0
  153. package/esm/trimStart.js.map +1 -0
  154. package/esm/utils/utils.d.ts +6 -0
  155. package/esm/utils/utils.d.ts.map +1 -0
  156. package/esm/utils/utils.js +7 -0
  157. package/esm/utils/utils.js.map +1 -0
  158. package/package.json +1 -1
  159. package/src/deserialize.spec.ts +12 -0
  160. package/src/index.ts +3 -0
  161. package/src/omit.ts +8 -2
  162. package/src/pick.ts +1 -1
  163. package/src/replaceDeep.spec.ts +91 -0
  164. package/src/replaceDeep.ts +22 -27
  165. package/src/replaceDeepByFn.spec.ts +162 -0
  166. package/src/replaceDeepByFn.ts +93 -0
  167. package/src/serialize.spec.ts +42 -0
  168. package/src/serialize.ts +65 -18
  169. package/src/trim.spec.ts +22 -0
  170. package/src/trim.ts +23 -0
  171. package/src/trimEnd.spec.ts +20 -0
  172. package/src/trimEnd.ts +22 -0
  173. package/src/trimStart.spec.ts +20 -0
  174. package/src/trimStart.ts +22 -0
  175. package/src/utils/utils.ts +11 -0
@@ -0,0 +1,93 @@
1
+ import { isPlainObject } from "./isPlainObject";
2
+
3
+ type Options = {
4
+ /**
5
+ * If true, the source objects and arrays will be mutated. Default is false.
6
+ */
7
+ mutate?: boolean;
8
+ /**
9
+ * If true, the function will go into instances for replacement. Otherwise, it will only replace properties of plain
10
+ * objects. Default is false.
11
+ * Warning: This option requires `mutate` to be enabled, because we can't clone instances.
12
+ */
13
+ replaceInstancesProps?: boolean;
14
+ };
15
+
16
+ /**
17
+ * Replaces all occurrences of `search` with `value` in `source` object/array. You do comparison by yourself by providing a callback function.
18
+ *
19
+ * Warnings:
20
+ * - By default, it does not mutate the `source`/deep objects/arrays, but it can be enabled with `mutate` option.
21
+ * - By default, it does not go into instances for replacement, only plain objects.
22
+ * - If your instances are cross-referenced, you may end up in an infinite loop.
23
+ *
24
+ * TypeScript users: This is way too dynamic to type properly, therefore, typing assumes the most basic form of
25
+ * replacement where search and value are of the same type. If that's not the case for you - you'll have to typecast.
26
+ *
27
+ * @param source - source object/array/value
28
+ * @param search - value to search for
29
+ * @param replaceWith - value to replace with
30
+ * @param options - optional options
31
+ */
32
+ const replaceDeepByFn = <T>( // eslint-disable-line max-statements
33
+ source: T, search: (value: unknown) => boolean, replaceWith: (value: unknown) => unknown, options?: Options,
34
+ ): T => {
35
+ if (options?.replaceInstancesProps && !options.mutate) {
36
+ throw new Error("`replaceInstancesProps` option requires `mutate` to be enabled");
37
+ }
38
+
39
+ const searchResult = search(source);
40
+
41
+ if (typeof searchResult !== "boolean") {
42
+ throw new Error("search function must return a boolean");
43
+ }
44
+
45
+ if (searchResult) {
46
+ return replaceWith(source) as T;
47
+ }
48
+
49
+ if (source == null) {
50
+ return source;
51
+ }
52
+
53
+ if (typeof source === "object") {
54
+ if (Array.isArray(source)) {
55
+ if (options?.mutate) {
56
+ for (let i = 0; i < source.length; i++) {
57
+ // eslint-disable-next-line no-param-reassign,@typescript-eslint/no-unsafe-assignment
58
+ source[i] = replaceDeepByFn(source[i], search, replaceWith, options);
59
+ }
60
+ return source;
61
+ }
62
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
63
+ return source.map((item) => replaceDeepByFn(item, search, replaceWith, options)) as unknown as T;
64
+ }
65
+
66
+ if (options?.mutate) {
67
+ if (isPlainObject(source) || options.replaceInstancesProps) {
68
+ for (const key of Object.keys(source)) {
69
+ // eslint-disable-next-line no-param-reassign
70
+ (source as Record<string, unknown>)[key] = replaceDeepByFn(
71
+ (source as Record<string, unknown>)[key], search, replaceWith, options,
72
+ );
73
+ }
74
+ }
75
+
76
+ return source;
77
+ }
78
+
79
+ if (isPlainObject(source)) {
80
+ return Object.keys(source).reduce<Record<string, unknown>>((acc, key) => {
81
+ // eslint-disable-next-line no-param-reassign
82
+ acc[key] = replaceDeepByFn((source as Record<string, unknown>)[key], search, replaceWith, options);
83
+ return acc;
84
+ }, {}) as T;
85
+ }
86
+ }
87
+
88
+ return source;
89
+ };
90
+
91
+ export {
92
+ replaceDeepByFn,
93
+ };
@@ -80,4 +80,46 @@ describe("serialize", () => {
80
80
 
81
81
  must(serialize(a)).equal(serialize(b));
82
82
  });
83
+
84
+ it("allows serializing dates and other objects containing .toJSON", async () => {
85
+ const date = new Date(1714390008941);
86
+ must(serialize(date, {
87
+ D: (value) => {
88
+ if (value instanceof Date) {
89
+ return String(value.getTime());
90
+ }
91
+ return null;
92
+ },
93
+ })).equal(`"D:1714390008941"`);
94
+ });
95
+
96
+ it("should avoid excessive calls to custom serializers", async () => {
97
+ const customSerializers: CustomSerializers = {
98
+ p: (value) => {
99
+ // console.log("Called with", typeof value, value);
100
+ if (value instanceof Person) {
101
+ return value.name;
102
+ }
103
+ return null;
104
+ },
105
+ };
106
+
107
+ const p1 = new Person("John");
108
+ const spy = jest.spyOn(customSerializers, "p");
109
+
110
+ serialize({
111
+ a: [p1],
112
+ b: 1,
113
+ c: {
114
+ d: true,
115
+ e: p1,
116
+ },
117
+ }, customSerializers);
118
+ expect(spy).toHaveBeenCalledTimes(4);
119
+ });
120
+
121
+ it("should not crash on unknown instances but serialize as plain object", async () => {
122
+ const p1 = new Person("John");
123
+ must(serialize(p1)).equal(`{"name":"s:John"}`);
124
+ });
83
125
  });
package/src/serialize.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import { sortProps } from "./sortProps.js";
2
+ import { replaceDeepByFn } from "./replaceDeepByFn.js";
3
+ import { isPlainObject } from "./isPlainObject.js";
4
+ import { DataWrapper } from "./utils/utils.js";
2
5
 
3
6
  type CustomSerializers = {
4
7
  [key: string]: (data: unknown) => (string | null);
@@ -33,6 +36,32 @@ type Options = {
33
36
  * @param options - options
34
37
  */
35
38
  const serialize = (data: unknown, customSerializers?: CustomSerializers, options?: Options) => { // eslint-disable-line max-lines-per-function,max-len
39
+ const sourceData = Object.keys(customSerializers ?? {}).length
40
+ ? replaceDeepByFn(
41
+ data,
42
+ value => {
43
+ if (["string", "number", "bigint", "undefined", "boolean"].includes(typeof value)) {
44
+ return false;
45
+ }
46
+ if (value === null || Array.isArray(value)) {
47
+ return false;
48
+ }
49
+ if (isPlainObject(value)) {
50
+ return false;
51
+ }
52
+
53
+ const serializerKeys = Object.keys(customSerializers ?? {});
54
+ for (const key of serializerKeys) {
55
+ const serialized = customSerializers![key]!(value);
56
+ if (typeof serialized === "string") {
57
+ return true;
58
+ }
59
+ }
60
+ return false;
61
+ },
62
+ value => new DataWrapper(value),
63
+ )
64
+ : data;
36
65
  const replacer = (_key: string, value: unknown) => { // eslint-disable-line max-statements
37
66
  if (typeof value === "string") {
38
67
  return `s:${value}`;
@@ -52,12 +81,22 @@ const serialize = (data: unknown, customSerializers?: CustomSerializers, options
52
81
  if (value === null) {
53
82
  return "l:";
54
83
  }
84
+ if (Array.isArray(value)) {
85
+ return value as unknown[];
86
+ }
87
+ if (isPlainObject(value)) {
88
+ return value;
89
+ }
55
90
  const serializerKeys = Object.keys(customSerializers ?? {});
56
91
  for (const key of serializerKeys) {
57
- const serialized = customSerializers![key]!(value);
92
+ const serialized = customSerializers![key]!(value instanceof DataWrapper ? value.data : value);
58
93
  if (typeof serialized === "string") {
59
94
  return `${key}:${serialized}`;
60
95
  }
96
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
97
+ if (serialized !== null) {
98
+ throw new Error(`Custom serializer for key ${key} returned a non-string value: ${String(serialized)}`);
99
+ }
61
100
  }
62
101
  if (typeof value === "object") {
63
102
  return value;
@@ -67,35 +106,43 @@ const serialize = (data: unknown, customSerializers?: CustomSerializers, options
67
106
  };
68
107
 
69
108
  if (
70
- data == null
71
- || typeof data === "string"
72
- || typeof data === "number"
73
- || typeof data === "bigint"
74
- || typeof data === "undefined"
75
- || typeof data === "boolean"
76
- || Array.isArray(data)
109
+ sourceData == null
110
+ || typeof sourceData === "string"
111
+ || typeof sourceData === "number"
112
+ || typeof sourceData === "bigint"
113
+ || typeof sourceData === "undefined"
114
+ || typeof sourceData === "boolean"
115
+ || Array.isArray(sourceData)
77
116
  ) {
78
- return JSON.stringify(data, replacer);
117
+ return JSON.stringify(sourceData, replacer);
79
118
  }
80
119
 
81
- const serializerKeysGlobal = Object.keys(customSerializers ?? {});
82
- for (const key of serializerKeysGlobal) {
83
- const serialized = customSerializers![key]!(data);
84
- if (typeof serialized === "string") {
85
- return `"${key}:${serialized}"`;
120
+ if (!isPlainObject(sourceData)) {
121
+ const serializerKeysGlobal = Object.keys(customSerializers ?? {});
122
+ for (const key of serializerKeysGlobal) {
123
+ const serialized = customSerializers![key]!(
124
+ sourceData instanceof DataWrapper ? sourceData.data : sourceData,
125
+ );
126
+ if (typeof serialized === "string") {
127
+ return `"${key}:${serialized}"`;
128
+ }
129
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
130
+ if (serialized !== null) {
131
+ throw new Error(`Custom serializer for key ${key} returned a non-string value: ${String(serialized)}`);
132
+ }
86
133
  }
87
134
  }
88
135
 
89
- if (typeof data === "object") {
136
+ if (typeof sourceData === "object") {
90
137
  return JSON.stringify(
91
138
  options?.sortProps === false
92
- ? data
93
- : sortProps(data as Record<string, unknown>),
139
+ ? sourceData
140
+ : sortProps(sourceData as Record<string, unknown>),
94
141
  replacer,
95
142
  );
96
143
  }
97
144
 
98
- throw new Error(`Unsupported data type: ${typeof data}`);
145
+ throw new Error(`Unsupported data type: ${typeof sourceData}`);
99
146
  };
100
147
 
101
148
  export type { CustomSerializers };
@@ -0,0 +1,22 @@
1
+ import { trim } from "./trim";
2
+
3
+ describe("trim", () => {
4
+ it("should trim single character from both sides of the string", async () => {
5
+ must(trim("aaacataaa", "a")).equal("cat");
6
+ must(trim("abacteria", "a")).equal("bacteri");
7
+ must(trim("abc", "a")).equal("bc");
8
+ must(trim("abc", "c")).equal("ab");
9
+ must(trim("aajjja", "a")).equal("jjj");
10
+ });
11
+
12
+ it("should trim multiple characters as a whole", async () => {
13
+ must(trim("abopopab", "ab")).equal("opop");
14
+ must(trim("atitleb", "ab")).equal("atitleb");
15
+ must(trim("abtitleab", "ab")).equal("title");
16
+ });
17
+
18
+ it("can trim into empty string", async () => {
19
+ must(trim("abc", "abc")).equal("");
20
+ must(trim("a", "a")).equal("");
21
+ });
22
+ });
package/src/trim.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { trimStart } from "./trimStart.js";
2
+ import { trimEnd } from "./trimEnd.js";
3
+
4
+ /**
5
+ * Removes given characters from both sides of the string.
6
+ * If you want to remove from one side of the string see {@link trimStart} and {@link trimEnd}.
7
+ *
8
+ * @param source - Source string.
9
+ * @param characters - Characters to remove, taken as a whole.
10
+ *
11
+ * @example
12
+ * trim("abcb", "ab"); // "cb"
13
+ * trim("!aaa!", "!"); // "aaa"
14
+ * trim("!aaa!!!", "!"); // "aaa"
15
+ * trim("!aaa!!!", "!!!"); // "!aaa"
16
+ */
17
+ const trim = (source: string, characters: string) => {
18
+ return trimStart(trimEnd(source, characters), characters);
19
+ };
20
+
21
+ export {
22
+ trim,
23
+ };
@@ -0,0 +1,20 @@
1
+ import { trimEnd } from "./trimEnd";
2
+
3
+ describe("trimEnd", () => {
4
+ it("should trim single character from the end of the string", async () => {
5
+ must(trimEnd("abc?", "?")).equal("abc");
6
+ must(trimEnd("a? b?c???", "?")).equal("a? b?c");
7
+ });
8
+
9
+ it("should trim multiple characters as a whole", async () => {
10
+ must(trimEnd("abc", "bc")).equal("a");
11
+
12
+ must(trimEnd("abcc", "bc")).equal("abcc");
13
+ must(trimEnd("abcb", "bc")).equal("abcb");
14
+ });
15
+
16
+ it("can trim into empty string", async () => {
17
+ must(trimEnd("abc", "abc")).equal("");
18
+ must(trimEnd("a", "a")).equal("");
19
+ });
20
+ });
package/src/trimEnd.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Removes given characters from the end of the string.
3
+ * See also: {@link trim} and {@link trimStart}.
4
+ *
5
+ * @param source - Source string.
6
+ * @param characters - Characters to remove, taken as a whole.
7
+ *
8
+ * @example
9
+ * trimEnd("abcxzyz", "yz"); // "abcxz"
10
+ * trimEnd("!aaa!!", "!"); // "!aaa"
11
+ */
12
+ const trimEnd = (source: string, characters: string) => {
13
+ let s = source;
14
+ while (s.endsWith(characters)) {
15
+ s = s.slice(0, -characters.length);
16
+ }
17
+ return s;
18
+ };
19
+
20
+ export {
21
+ trimEnd,
22
+ };
@@ -0,0 +1,20 @@
1
+ import { trimStart } from "./trimStart";
2
+
3
+ describe("trimStart", () => {
4
+ it("should trim single character from the start of the string", async () => {
5
+ must(trimStart("?abc", "?")).equal("abc");
6
+ must(trimStart("aaabc", "a")).equal("bc");
7
+ });
8
+
9
+ it("should trim multiple characters as a whole", async () => {
10
+ must(trimStart("abc", "ab")).equal("c");
11
+
12
+ must(trimStart("aabcc", "ab")).equal("aabcc");
13
+ must(trimStart("babcb", "ab")).equal("babcb");
14
+ });
15
+
16
+ it("should trim into empty string if needed", async () => {
17
+ must(trimStart("abc", "abc")).equal("");
18
+ must(trimStart("aaa", "a")).equal("");
19
+ });
20
+ });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Removes given characters from the start of the string.
3
+ * See also: {@link trim} and {@link trimEnd}.
4
+ *
5
+ * @param source - Source string.
6
+ * @param characters - Characters to remove, taken as a whole.
7
+ *
8
+ * @example
9
+ * trimStart("abbcb", "ab"); // "bcb"
10
+ * trimStart("!!aaa!", "!"); // "aaa!"
11
+ */
12
+ const trimStart = (source: string, characters: string) => {
13
+ let s = source;
14
+ while (s.startsWith(characters)) {
15
+ s = s.slice(characters.length);
16
+ }
17
+ return s;
18
+ };
19
+
20
+ export {
21
+ trimStart,
22
+ };
@@ -0,0 +1,11 @@
1
+ class DataWrapper<T> {
2
+ public data: T;
3
+
4
+ public constructor(data: T) {
5
+ this.data = data;
6
+ }
7
+ }
8
+
9
+ export {
10
+ DataWrapper,
11
+ };