@typia/utils 12.0.0-dev.20260309 → 12.0.0-dev.20260310

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 (92) hide show
  1. package/lib/http/internal/HttpLlmApplicationComposer.mjs +5 -1
  2. package/lib/http/internal/HttpLlmApplicationComposer.mjs.map +1 -1
  3. package/lib/index.mjs +9 -9
  4. package/lib/utils/LlmJson.mjs +9 -2
  5. package/lib/utils/LlmJson.mjs.map +1 -1
  6. package/lib/utils/internal/stringifyValidationFailure.js +17 -15
  7. package/lib/utils/internal/stringifyValidationFailure.js.map +1 -1
  8. package/lib/utils/internal/stringifyValidationFailure.mjs +17 -15
  9. package/lib/utils/internal/stringifyValidationFailure.mjs.map +1 -1
  10. package/lib/validators/internal/OpenApiOneOfValidator.mjs +5 -1
  11. package/lib/validators/internal/OpenApiOneOfValidator.mjs.map +1 -1
  12. package/package.json +2 -2
  13. package/src/converters/LlmSchemaConverter.ts +647 -647
  14. package/src/converters/OpenApiConverter.ts +285 -285
  15. package/src/converters/index.ts +5 -5
  16. package/src/converters/internal/LlmDescriptionInverter.ts +178 -178
  17. package/src/converters/internal/LlmParametersComposer.ts +52 -52
  18. package/src/converters/internal/OpenApiConstraintShifter.ts +154 -154
  19. package/src/converters/internal/OpenApiExclusiveEmender.ts +46 -46
  20. package/src/converters/internal/OpenApiV3Downgrader.ts +355 -355
  21. package/src/converters/internal/OpenApiV3Upgrader.ts +470 -470
  22. package/src/converters/internal/OpenApiV3_1Upgrader.ts +685 -685
  23. package/src/converters/internal/SwaggerV2Downgrader.ts +424 -424
  24. package/src/converters/internal/SwaggerV2Upgrader.ts +523 -523
  25. package/src/http/HttpError.ts +107 -107
  26. package/src/http/HttpLlm.ts +167 -167
  27. package/src/http/HttpMigration.ts +92 -92
  28. package/src/http/index.ts +3 -3
  29. package/src/http/internal/HttpLlmApplicationComposer.ts +361 -361
  30. package/src/http/internal/HttpLlmFunctionFetcher.ts +37 -37
  31. package/src/http/internal/HttpMigrateApplicationComposer.ts +56 -56
  32. package/src/http/internal/HttpMigrateRouteAccessor.ts +135 -135
  33. package/src/http/internal/HttpMigrateRouteComposer.ts +505 -505
  34. package/src/http/internal/HttpMigrateRouteFetcher.ts +203 -203
  35. package/src/index.ts +4 -4
  36. package/src/utils/ArrayUtil.ts +42 -42
  37. package/src/utils/LlmJson.ts +141 -141
  38. package/src/utils/MapUtil.ts +15 -15
  39. package/src/utils/NamingConvention.ts +205 -205
  40. package/src/utils/Singleton.ts +17 -17
  41. package/src/utils/StringUtil.ts +14 -14
  42. package/src/utils/dedent.ts +57 -57
  43. package/src/utils/index.ts +8 -8
  44. package/src/utils/internal/EndpointUtil.ts +44 -44
  45. package/src/utils/internal/JsonDescriptor.ts +70 -70
  46. package/src/utils/internal/OpenApiTypeCheckerBase.ts +822 -822
  47. package/src/utils/internal/coerceLlmArguments.ts +314 -314
  48. package/src/utils/internal/parseLenientJson.ts +894 -894
  49. package/src/utils/internal/stringifyValidationFailure.ts +415 -411
  50. package/src/validators/LlmTypeChecker.ts +402 -402
  51. package/src/validators/OpenApiTypeChecker.ts +297 -297
  52. package/src/validators/OpenApiV3TypeChecker.ts +70 -70
  53. package/src/validators/OpenApiV3_1TypeChecker.ts +86 -86
  54. package/src/validators/OpenApiValidator.ts +94 -94
  55. package/src/validators/SwaggerV2TypeChecker.ts +71 -71
  56. package/src/validators/functional/_isBigintString.ts +8 -8
  57. package/src/validators/functional/_isFormatByte.ts +7 -7
  58. package/src/validators/functional/_isFormatDate.ts +3 -3
  59. package/src/validators/functional/_isFormatDateTime.ts +4 -4
  60. package/src/validators/functional/_isFormatDuration.ts +4 -4
  61. package/src/validators/functional/_isFormatEmail.ts +4 -4
  62. package/src/validators/functional/_isFormatHostname.ts +4 -4
  63. package/src/validators/functional/_isFormatIdnEmail.ts +4 -4
  64. package/src/validators/functional/_isFormatIdnHostname.ts +4 -4
  65. package/src/validators/functional/_isFormatIpv4.ts +4 -4
  66. package/src/validators/functional/_isFormatIpv6.ts +4 -4
  67. package/src/validators/functional/_isFormatIri.ts +3 -3
  68. package/src/validators/functional/_isFormatIriReference.ts +4 -4
  69. package/src/validators/functional/_isFormatJsonPointer.ts +3 -3
  70. package/src/validators/functional/_isFormatPassword.ts +1 -1
  71. package/src/validators/functional/_isFormatRegex.ts +8 -8
  72. package/src/validators/functional/_isFormatRelativeJsonPointer.ts +4 -4
  73. package/src/validators/functional/_isFormatTime.ts +4 -4
  74. package/src/validators/functional/_isFormatUri.ts +6 -6
  75. package/src/validators/functional/_isFormatUriReference.ts +5 -5
  76. package/src/validators/functional/_isFormatUriTemplate.ts +4 -4
  77. package/src/validators/functional/_isFormatUrl.ts +4 -4
  78. package/src/validators/functional/_isFormatUuid.ts +3 -3
  79. package/src/validators/functional/_isUniqueItems.ts +159 -159
  80. package/src/validators/index.ts +14 -14
  81. package/src/validators/internal/IOpenApiValidatorContext.ts +17 -17
  82. package/src/validators/internal/OpenApiArrayValidator.ts +49 -49
  83. package/src/validators/internal/OpenApiBooleanValidator.ts +11 -11
  84. package/src/validators/internal/OpenApiConstantValidator.ts +11 -11
  85. package/src/validators/internal/OpenApiIntegerValidator.ts +49 -49
  86. package/src/validators/internal/OpenApiNumberValidator.ts +48 -48
  87. package/src/validators/internal/OpenApiObjectValidator.ts +83 -83
  88. package/src/validators/internal/OpenApiOneOfValidator.ts +309 -309
  89. package/src/validators/internal/OpenApiSchemaNamingRule.ts +124 -124
  90. package/src/validators/internal/OpenApiStationValidator.ts +115 -115
  91. package/src/validators/internal/OpenApiStringValidator.ts +88 -88
  92. package/src/validators/internal/OpenApiTupleValidator.ts +55 -55
@@ -1,203 +1,203 @@
1
- import { IHttpConnection, IHttpResponse } from "@typia/interface";
2
-
3
- import { HttpError } from "../HttpError";
4
- import type { HttpMigration } from "../HttpMigration";
5
-
6
- export namespace HttpMigrateRouteFetcher {
7
- export const execute = async (
8
- props: HttpMigration.IFetchProps,
9
- ): Promise<unknown> => {
10
- const result: IHttpResponse = await _Propagate("request", props);
11
- props.route.success?.media;
12
- if (result.status !== 200 && result.status !== 201)
13
- throw new HttpError(
14
- props.route.method.toUpperCase() as "GET",
15
- props.route.path,
16
- result.status,
17
- result.headers,
18
- result.body as string,
19
- );
20
- return result.body;
21
- };
22
-
23
- export const propagate = (
24
- props: HttpMigration.IFetchProps,
25
- ): Promise<IHttpResponse> => _Propagate("propagate", props);
26
- }
27
-
28
- const _Propagate = async (
29
- from: string,
30
- props: HttpMigration.IFetchProps,
31
- ): Promise<IHttpResponse> => {
32
- // VALIDATE PARAMETERS
33
- const error = (message: string) =>
34
- new Error(`Error on MigrateRouteFetcher.${from}(): ${message}`);
35
- if (Array.isArray(props.parameters)) {
36
- if (props.route.parameters.length !== props.parameters.length)
37
- throw error(`number of parameters is not matched.`);
38
- } else if (
39
- props.route.parameters.every(
40
- (p) => (props.parameters as Record<string, any>)[p.key] !== undefined,
41
- ) === false
42
- )
43
- throw error(`number of parameters is not matched.`);
44
-
45
- // VALIDATE QUERY
46
- if (!!props.route.query !== !!props.query)
47
- throw error(`query is not matched.`);
48
- else if (!!props.route.body !== (props.body !== undefined))
49
- throw error(`body is not matched.`);
50
-
51
- // INIT REQUEST DATA
52
- const headers: Record<string, IHttpConnection.HeaderValue | undefined> = {
53
- ...(props.connection.headers ?? {}),
54
- ...(props.route.body?.type &&
55
- props.route.body.type !== "multipart/form-data"
56
- ? { "Content-Type": props.route.body.type }
57
- : {}),
58
- };
59
- const init: RequestInit = {
60
- ...(props.connection.options ?? {}),
61
- method: props.route.method.toUpperCase(),
62
- headers: (() => {
63
- const output: [string, string][] = [];
64
- for (const [key, value] of Object.entries(headers))
65
- if (value === undefined) continue;
66
- else if (Array.isArray(value))
67
- for (const v of value) output.push([key, String(v)]);
68
- else output.push([key, String(value)]);
69
- return output;
70
- })(),
71
- };
72
- if (props.body !== undefined)
73
- init.body = (
74
- props.route.body?.type === "application/x-www-form-urlencoded"
75
- ? requestQueryBody(props.body)
76
- : props.route.body?.type === "multipart/form-data"
77
- ? requestFormDataBody(props.body)
78
- : props.route.body?.type === "application/json"
79
- ? JSON.stringify(props.body)
80
- : props.body
81
- ) as any;
82
-
83
- // DO REQUEST
84
- const resolvedPath: string =
85
- props.connection.host.endsWith("/") === false &&
86
- props.route.emendedPath.startsWith("/") === false
87
- ? `/${getPath(props)}`
88
- : getPath(props);
89
- const url: URL = new URL(`${props.connection.host}${resolvedPath}`);
90
-
91
- const response: Response = await (props.connection.fetch ?? fetch)(url, init);
92
- const status: number = response.status;
93
- const out = (body: unknown): IHttpResponse => ({
94
- status,
95
- headers: responseHeaders(response.headers),
96
- body,
97
- });
98
-
99
- if (status === 200 || status === 201) {
100
- // SUCCESS CASE
101
- if (props.route.method.toUpperCase() === "HEAD") return out(undefined);
102
- else if (
103
- props.route.success === null ||
104
- props.route.success.type === "text/plain"
105
- )
106
- return out(await response.text());
107
- else if (props.route.success.type === "application/json") {
108
- const text: string = await response.text();
109
- return out(text.length ? JSON.parse(text) : undefined);
110
- } else if (props.route.success.type === "application/x-www-form-urlencoded")
111
- return out(new URLSearchParams(await response.text()));
112
- else if (props.route.success.type === "multipart/form-data")
113
- return out(await response.formData());
114
- throw error("Unsupported response body type.");
115
- } else {
116
- // FAILURE CASE
117
- const type: string = (
118
- response.headers.get("content-type") ??
119
- response.headers.get("Content-Type") ??
120
- ""
121
- )
122
- .split(";")[0]!
123
- .trim();
124
- if (type === "" || type.startsWith("text/"))
125
- return out(await response.text());
126
- else if (type === "application/json") return out(await response.json());
127
- else if (type === "application/x-www-form-urlencoded")
128
- return out(new URLSearchParams(await response.text()));
129
- else if (type === "multipart/form-data")
130
- return out(await response.formData());
131
- else if (type === "application/octet-stream")
132
- return out(await response.blob());
133
- return out(await response.text());
134
- }
135
- };
136
-
137
- const getPath = (
138
- props: Pick<HttpMigration.IFetchProps, "route" | "parameters" | "query">,
139
- ): string => {
140
- let path: string = props.route.emendedPath;
141
- props.route.parameters.forEach((p, i) => {
142
- path = path.replace(
143
- `:${p.key}`,
144
- encodeURIComponent(
145
- String(
146
- (Array.isArray(props.parameters)
147
- ? props.parameters[i]
148
- : props.parameters[p.key]) ?? "null",
149
- ),
150
- ),
151
- );
152
- });
153
- if (props.route.query) path += getQueryPath(props.query ?? {});
154
- return path;
155
- };
156
-
157
- const getQueryPath = (query: Record<string, any>): string => {
158
- const variables = new URLSearchParams();
159
- for (const [key, value] of Object.entries(query))
160
- if (undefined === value) continue;
161
- else if (Array.isArray(value))
162
- value.forEach((elem: any) => variables.append(key, String(elem)));
163
- else variables.set(key, String(value));
164
- return 0 === variables.size ? "" : `?${variables.toString()}`;
165
- };
166
-
167
- const requestQueryBody = (input: any): URLSearchParams => {
168
- const q: URLSearchParams = new URLSearchParams();
169
- for (const [key, value] of Object.entries(input))
170
- if (value === undefined) continue;
171
- else if (Array.isArray(value))
172
- value.forEach((elem) => q.append(key, String(elem)));
173
- else q.set(key, String(value));
174
- return q;
175
- };
176
- const requestFormDataBody = (input: Record<string, any>): FormData => {
177
- const encoded: FormData = new FormData();
178
- const append = (key: string) => (value: any) => {
179
- if (value === undefined) return;
180
- else if (typeof File === "function" && value instanceof File)
181
- encoded.append(key, value, value.name);
182
- else encoded.append(key, value);
183
- };
184
- for (const [key, value] of Object.entries(input))
185
- if (Array.isArray(value)) value.map(append(key));
186
- else append(key)(value);
187
- return encoded;
188
- };
189
-
190
- const responseHeaders = (
191
- headers: Headers,
192
- ): Record<string, string | string[]> => {
193
- const output: Record<string, string | string[]> = {};
194
- headers.forEach((value, key) => {
195
- if (key === "set-cookie") {
196
- output[key] ??= [];
197
- (output[key] as string[]).push(
198
- ...value.split(";").map((str) => str.trim()),
199
- );
200
- } else output[key] = value;
201
- });
202
- return output;
203
- };
1
+ import { IHttpConnection, IHttpResponse } from "@typia/interface";
2
+
3
+ import { HttpError } from "../HttpError";
4
+ import type { HttpMigration } from "../HttpMigration";
5
+
6
+ export namespace HttpMigrateRouteFetcher {
7
+ export const execute = async (
8
+ props: HttpMigration.IFetchProps,
9
+ ): Promise<unknown> => {
10
+ const result: IHttpResponse = await _Propagate("request", props);
11
+ props.route.success?.media;
12
+ if (result.status !== 200 && result.status !== 201)
13
+ throw new HttpError(
14
+ props.route.method.toUpperCase() as "GET",
15
+ props.route.path,
16
+ result.status,
17
+ result.headers,
18
+ result.body as string,
19
+ );
20
+ return result.body;
21
+ };
22
+
23
+ export const propagate = (
24
+ props: HttpMigration.IFetchProps,
25
+ ): Promise<IHttpResponse> => _Propagate("propagate", props);
26
+ }
27
+
28
+ const _Propagate = async (
29
+ from: string,
30
+ props: HttpMigration.IFetchProps,
31
+ ): Promise<IHttpResponse> => {
32
+ // VALIDATE PARAMETERS
33
+ const error = (message: string) =>
34
+ new Error(`Error on MigrateRouteFetcher.${from}(): ${message}`);
35
+ if (Array.isArray(props.parameters)) {
36
+ if (props.route.parameters.length !== props.parameters.length)
37
+ throw error(`number of parameters is not matched.`);
38
+ } else if (
39
+ props.route.parameters.every(
40
+ (p) => (props.parameters as Record<string, any>)[p.key] !== undefined,
41
+ ) === false
42
+ )
43
+ throw error(`number of parameters is not matched.`);
44
+
45
+ // VALIDATE QUERY
46
+ if (!!props.route.query !== !!props.query)
47
+ throw error(`query is not matched.`);
48
+ else if (!!props.route.body !== (props.body !== undefined))
49
+ throw error(`body is not matched.`);
50
+
51
+ // INIT REQUEST DATA
52
+ const headers: Record<string, IHttpConnection.HeaderValue | undefined> = {
53
+ ...(props.connection.headers ?? {}),
54
+ ...(props.route.body?.type &&
55
+ props.route.body.type !== "multipart/form-data"
56
+ ? { "Content-Type": props.route.body.type }
57
+ : {}),
58
+ };
59
+ const init: RequestInit = {
60
+ ...(props.connection.options ?? {}),
61
+ method: props.route.method.toUpperCase(),
62
+ headers: (() => {
63
+ const output: [string, string][] = [];
64
+ for (const [key, value] of Object.entries(headers))
65
+ if (value === undefined) continue;
66
+ else if (Array.isArray(value))
67
+ for (const v of value) output.push([key, String(v)]);
68
+ else output.push([key, String(value)]);
69
+ return output;
70
+ })(),
71
+ };
72
+ if (props.body !== undefined)
73
+ init.body = (
74
+ props.route.body?.type === "application/x-www-form-urlencoded"
75
+ ? requestQueryBody(props.body)
76
+ : props.route.body?.type === "multipart/form-data"
77
+ ? requestFormDataBody(props.body)
78
+ : props.route.body?.type === "application/json"
79
+ ? JSON.stringify(props.body)
80
+ : props.body
81
+ ) as any;
82
+
83
+ // DO REQUEST
84
+ const resolvedPath: string =
85
+ props.connection.host.endsWith("/") === false &&
86
+ props.route.emendedPath.startsWith("/") === false
87
+ ? `/${getPath(props)}`
88
+ : getPath(props);
89
+ const url: URL = new URL(`${props.connection.host}${resolvedPath}`);
90
+
91
+ const response: Response = await (props.connection.fetch ?? fetch)(url, init);
92
+ const status: number = response.status;
93
+ const out = (body: unknown): IHttpResponse => ({
94
+ status,
95
+ headers: responseHeaders(response.headers),
96
+ body,
97
+ });
98
+
99
+ if (status === 200 || status === 201) {
100
+ // SUCCESS CASE
101
+ if (props.route.method.toUpperCase() === "HEAD") return out(undefined);
102
+ else if (
103
+ props.route.success === null ||
104
+ props.route.success.type === "text/plain"
105
+ )
106
+ return out(await response.text());
107
+ else if (props.route.success.type === "application/json") {
108
+ const text: string = await response.text();
109
+ return out(text.length ? JSON.parse(text) : undefined);
110
+ } else if (props.route.success.type === "application/x-www-form-urlencoded")
111
+ return out(new URLSearchParams(await response.text()));
112
+ else if (props.route.success.type === "multipart/form-data")
113
+ return out(await response.formData());
114
+ throw error("Unsupported response body type.");
115
+ } else {
116
+ // FAILURE CASE
117
+ const type: string = (
118
+ response.headers.get("content-type") ??
119
+ response.headers.get("Content-Type") ??
120
+ ""
121
+ )
122
+ .split(";")[0]!
123
+ .trim();
124
+ if (type === "" || type.startsWith("text/"))
125
+ return out(await response.text());
126
+ else if (type === "application/json") return out(await response.json());
127
+ else if (type === "application/x-www-form-urlencoded")
128
+ return out(new URLSearchParams(await response.text()));
129
+ else if (type === "multipart/form-data")
130
+ return out(await response.formData());
131
+ else if (type === "application/octet-stream")
132
+ return out(await response.blob());
133
+ return out(await response.text());
134
+ }
135
+ };
136
+
137
+ const getPath = (
138
+ props: Pick<HttpMigration.IFetchProps, "route" | "parameters" | "query">,
139
+ ): string => {
140
+ let path: string = props.route.emendedPath;
141
+ props.route.parameters.forEach((p, i) => {
142
+ path = path.replace(
143
+ `:${p.key}`,
144
+ encodeURIComponent(
145
+ String(
146
+ (Array.isArray(props.parameters)
147
+ ? props.parameters[i]
148
+ : props.parameters[p.key]) ?? "null",
149
+ ),
150
+ ),
151
+ );
152
+ });
153
+ if (props.route.query) path += getQueryPath(props.query ?? {});
154
+ return path;
155
+ };
156
+
157
+ const getQueryPath = (query: Record<string, any>): string => {
158
+ const variables = new URLSearchParams();
159
+ for (const [key, value] of Object.entries(query))
160
+ if (undefined === value) continue;
161
+ else if (Array.isArray(value))
162
+ value.forEach((elem: any) => variables.append(key, String(elem)));
163
+ else variables.set(key, String(value));
164
+ return 0 === variables.size ? "" : `?${variables.toString()}`;
165
+ };
166
+
167
+ const requestQueryBody = (input: any): URLSearchParams => {
168
+ const q: URLSearchParams = new URLSearchParams();
169
+ for (const [key, value] of Object.entries(input))
170
+ if (value === undefined) continue;
171
+ else if (Array.isArray(value))
172
+ value.forEach((elem) => q.append(key, String(elem)));
173
+ else q.set(key, String(value));
174
+ return q;
175
+ };
176
+ const requestFormDataBody = (input: Record<string, any>): FormData => {
177
+ const encoded: FormData = new FormData();
178
+ const append = (key: string) => (value: any) => {
179
+ if (value === undefined) return;
180
+ else if (typeof File === "function" && value instanceof File)
181
+ encoded.append(key, value, value.name);
182
+ else encoded.append(key, value);
183
+ };
184
+ for (const [key, value] of Object.entries(input))
185
+ if (Array.isArray(value)) value.map(append(key));
186
+ else append(key)(value);
187
+ return encoded;
188
+ };
189
+
190
+ const responseHeaders = (
191
+ headers: Headers,
192
+ ): Record<string, string | string[]> => {
193
+ const output: Record<string, string | string[]> = {};
194
+ headers.forEach((value, key) => {
195
+ if (key === "set-cookie") {
196
+ output[key] ??= [];
197
+ (output[key] as string[]).push(
198
+ ...value.split(";").map((str) => str.trim()),
199
+ );
200
+ } else output[key] = value;
201
+ });
202
+ return output;
203
+ };
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from "./converters/index";
2
- export * from "./http/index";
3
- export * from "./utils/index";
4
- export * from "./validators/index";
1
+ export * from "./converters/index";
2
+ export * from "./http/index";
3
+ export * from "./utils/index";
4
+ export * from "./validators/index";
@@ -1,42 +1,42 @@
1
- /** @internal */
2
- export namespace ArrayUtil {
3
- export const has = <T>(array: T[], pred: (elem: T) => boolean): boolean =>
4
- array.some(pred);
5
-
6
- export const add = <T>(
7
- array: T[],
8
- value: T,
9
- pred: (x: T, y: T) => boolean = (x, y) => x === y,
10
- ): boolean => {
11
- if (array.some((elem) => pred(elem, value))) return false;
12
- array.push(value);
13
- return true;
14
- };
15
-
16
- export const set = <Key, T>(
17
- array: T[],
18
- value: T,
19
- key: (elem: T) => Key,
20
- ): void => {
21
- if (array.some((elem) => key(elem) === key(value))) return;
22
- array.push(value);
23
- };
24
-
25
- export const take = <T>(
26
- array: T[],
27
- pred: (elem: T) => boolean,
28
- init: () => T,
29
- ): T => {
30
- const index: number = array.findIndex(pred);
31
- if (index !== -1) return array[index]!;
32
-
33
- const elem: T = init();
34
- array.push(elem);
35
- return elem;
36
- };
37
-
38
- export const repeat = <T>(
39
- count: number,
40
- closure: (index: number, count: number) => T,
41
- ): T[] => new Array(count).fill("").map((_, index) => closure(index, count));
42
- }
1
+ /** @internal */
2
+ export namespace ArrayUtil {
3
+ export const has = <T>(array: T[], pred: (elem: T) => boolean): boolean =>
4
+ array.some(pred);
5
+
6
+ export const add = <T>(
7
+ array: T[],
8
+ value: T,
9
+ pred: (x: T, y: T) => boolean = (x, y) => x === y,
10
+ ): boolean => {
11
+ if (array.some((elem) => pred(elem, value))) return false;
12
+ array.push(value);
13
+ return true;
14
+ };
15
+
16
+ export const set = <Key, T>(
17
+ array: T[],
18
+ value: T,
19
+ key: (elem: T) => Key,
20
+ ): void => {
21
+ if (array.some((elem) => key(elem) === key(value))) return;
22
+ array.push(value);
23
+ };
24
+
25
+ export const take = <T>(
26
+ array: T[],
27
+ pred: (elem: T) => boolean,
28
+ init: () => T,
29
+ ): T => {
30
+ const index: number = array.findIndex(pred);
31
+ if (index !== -1) return array[index]!;
32
+
33
+ const elem: T = init();
34
+ array.push(elem);
35
+ return elem;
36
+ };
37
+
38
+ export const repeat = <T>(
39
+ count: number,
40
+ closure: (index: number, count: number) => T,
41
+ ): T[] => new Array(count).fill("").map((_, index) => closure(index, count));
42
+ }