@goast/core 0.5.0-beta.5 → 0.5.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 (52) hide show
  1. package/LICENSE +21 -21
  2. package/esm/src/utils/api-data.utils.d.ts +2 -2
  3. package/esm/src/utils/api-data.utils.d.ts.map +1 -1
  4. package/package.json +1 -1
  5. package/script/src/utils/api-data.utils.d.ts +2 -2
  6. package/script/src/utils/api-data.utils.d.ts.map +1 -1
  7. package/src/_dnt.shims.ts +69 -0
  8. package/src/mod.ts +35 -0
  9. package/src/src/ast/node.ts +46 -0
  10. package/src/src/codegen/config.ts +41 -0
  11. package/src/src/codegen/endpoints-generator.ts +72 -0
  12. package/src/src/codegen/generator.ts +192 -0
  13. package/src/src/codegen/internal-utils.ts +6 -0
  14. package/src/src/codegen/schemas-generator.ts +87 -0
  15. package/src/src/codegen/services-generator.ts +77 -0
  16. package/src/src/codegen/types.ts +37 -0
  17. package/src/src/collect/collector.ts +142 -0
  18. package/src/src/collect/helpers.ts +36 -0
  19. package/src/src/collect/types.ts +21 -0
  20. package/src/src/internal-utils.ts +5 -0
  21. package/src/src/parse/deref-proxy.ts +64 -0
  22. package/src/src/parse/openapi-types.ts +949 -0
  23. package/src/src/parse/parser.ts +226 -0
  24. package/src/src/parse/types.ts +27 -0
  25. package/src/src/transform/api-types.ts +245 -0
  26. package/src/src/transform/helpers.ts +217 -0
  27. package/src/src/transform/transform-document.ts +35 -0
  28. package/src/src/transform/transform-endpoint.ts +331 -0
  29. package/src/src/transform/transform-schema.ts +217 -0
  30. package/src/src/transform/transformer.ts +53 -0
  31. package/src/src/transform/types.ts +52 -0
  32. package/src/src/utils/action-provider.ts +58 -0
  33. package/src/src/utils/api-data.utils.ts +48 -0
  34. package/src/src/utils/array.utils.ts +7 -0
  35. package/src/src/utils/asset-manager.ts +51 -0
  36. package/src/src/utils/class.utils.ts +40 -0
  37. package/src/src/utils/common.utils.ts +86 -0
  38. package/src/src/utils/condition.ts +7 -0
  39. package/src/src/utils/endpoint.utils.ts +10 -0
  40. package/src/src/utils/error.utils.ts +3 -0
  41. package/src/src/utils/factory.ts +42 -0
  42. package/src/src/utils/file-system.utils.ts +61 -0
  43. package/src/src/utils/generator.utils.ts +6 -0
  44. package/src/src/utils/object.utils.ts +32 -0
  45. package/src/src/utils/schema.utils.ts +148 -0
  46. package/src/src/utils/source-builder.ts +597 -0
  47. package/src/src/utils/string-builder/options.ts +19 -0
  48. package/src/src/utils/string-builder/string-builder.ts +165 -0
  49. package/src/src/utils/string-builder/utils.ts +69 -0
  50. package/src/src/utils/string.utils.ts +369 -0
  51. package/src/src/utils/string.utils.types.ts +179 -0
  52. package/src/src/utils/type.utils.ts +93 -0
@@ -0,0 +1,217 @@
1
+ import type { OpenApiSchema } from '../parse/openapi-types.js';
2
+ import type { Deref } from '../parse/types.js';
3
+ import { createOverwriteProxy } from '../utils/object.utils.js';
4
+ import type { ApiSchema, ApiSchemaExtensions, ApiSchemaKind } from './api-types.js';
5
+ import {
6
+ determineSchemaAccessibility,
7
+ determineSchemaKind,
8
+ determineSchemaName,
9
+ getCustomFields,
10
+ getOpenApiObjectIdentifier,
11
+ transformAdditionalProperties,
12
+ transformSchemaProperties,
13
+ } from './helpers.js';
14
+ import type { IncompleteApiSchema, OpenApiTransformerContext } from './types.js';
15
+
16
+ export function transformSchema<T extends Deref<OpenApiSchema>>(ctx: OpenApiTransformerContext, schema: T): ApiSchema {
17
+ if (!schema) {
18
+ throw new Error('Schema is required.');
19
+ }
20
+ const schemaSource = `${schema.$src.file}:${schema.$src.path}`;
21
+ const existingSchema = ctx.schemas.get(schemaSource) ?? ctx.incompleteSchemas.get(schemaSource);
22
+ if (existingSchema) return existingSchema as ApiSchema;
23
+
24
+ const openApiObjectId = getOpenApiObjectIdentifier(schema);
25
+ const existing = ctx.transformed.schemas.get(openApiObjectId);
26
+ if (existing) return existing;
27
+
28
+ let kind = determineSchemaKind(ctx, schema);
29
+ let nullable = kind === 'null';
30
+ if (kind === 'multi-type') {
31
+ const types = schema.type as string[];
32
+ let isSingleType = types.length === 1;
33
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
34
+ if (types.includes('null') || types.includes(null!)) {
35
+ nullable = true;
36
+ isSingleType = types.length === 2;
37
+ }
38
+
39
+ if (isSingleType) {
40
+ const newType = types.filter((t) => t !== 'null' && t !== null)[0];
41
+ schema = createOverwriteProxy(schema);
42
+ schema.type = newType;
43
+ kind = determineSchemaKind(ctx, schema);
44
+ } else {
45
+ schema.type = types.filter((t) => t !== 'null' && t !== null);
46
+ }
47
+ }
48
+
49
+ const id = ctx.idGenerator.generateId('schema');
50
+ const nameInfo = determineSchemaName(schema, id);
51
+ const incompleteSchema: IncompleteApiSchema = {
52
+ $src: {
53
+ ...schema.$src,
54
+ component: schema,
55
+ },
56
+ $ref: undefined,
57
+ id,
58
+ name: nameInfo.name,
59
+ isNameGenerated: nameInfo.isGenerated,
60
+ description: schema.description,
61
+ deprecated: schema.deprecated ?? false,
62
+ accessibility: determineSchemaAccessibility(schema),
63
+ kind,
64
+ enum: schema.enum,
65
+ default: schema.default,
66
+ example: schema.example,
67
+ nullable: nullable || schema.nullable === true,
68
+ required: new Set(schema.required),
69
+ custom: getCustomFields(schema),
70
+ not: schema.not ? transformSchema(ctx, schema.not) : undefined,
71
+ const: schema.const,
72
+ discriminator: schema.discriminator
73
+ ? {
74
+ propertyName: typeof schema.discriminator === 'string'
75
+ ? schema.discriminator
76
+ : schema.discriminator.propertyName,
77
+ mapping: {},
78
+ }
79
+ : undefined,
80
+ inheritedSchemas: [],
81
+ };
82
+ ctx.incompleteSchemas.set(schemaSource, incompleteSchema);
83
+
84
+ if (schema.$ref) {
85
+ incompleteSchema.$ref = transformSchema(ctx, schema.$ref);
86
+ }
87
+ const extensions = schemaTransformers[kind](schema, ctx);
88
+ const completeSchema = Object.assign(incompleteSchema, extensions) as
89
+ & IncompleteApiSchema
90
+ & ApiSchemaExtensions<ApiSchemaKind>;
91
+ resolveDescriminatorMapping(ctx, completeSchema);
92
+
93
+ ctx.incompleteSchemas.delete(schemaSource);
94
+ ctx.transformed.schemas.set(openApiObjectId, completeSchema);
95
+ ctx.schemas.set(schemaSource, completeSchema);
96
+ return completeSchema;
97
+ }
98
+
99
+ function allExtensionsTransformer(schema: Deref<OpenApiSchema>, context: OpenApiTransformerContext) {
100
+ return {
101
+ oneOf: schema.oneOf?.map((s) => transformSchema(context, s)) ?? [],
102
+ format: schema.format,
103
+ pattern: schema.pattern,
104
+ minLength: schema.minLength,
105
+ maxLength: schema.maxLength,
106
+ minimum: schema.minimum,
107
+ maximum: schema.maximum,
108
+ properties: transformSchemaProperties<Deref<OpenApiSchema>>(context, schema, transformSchema),
109
+ additionalProperties: transformAdditionalProperties(context, schema, transformSchema),
110
+ allOf: schema.allOf?.map((s) => transformSchema(context, s)) ?? [],
111
+ anyOf: schema.anyOf?.map((s) => transformSchema(context, s)) ?? [],
112
+ items: schema.items ? transformSchema(context, schema.items) : undefined,
113
+ minItems: schema.minItems,
114
+ maxItems: schema.maxItems,
115
+ };
116
+ }
117
+
118
+ const schemaTransformers: {
119
+ [K in ApiSchemaKind]: (
120
+ schema: Deref<OpenApiSchema>,
121
+ context: OpenApiTransformerContext,
122
+ ) => Omit<ApiSchemaExtensions<K>, 'kind'>;
123
+ } = {
124
+ oneOf: (schema, context) => ({
125
+ ...allExtensionsTransformer(schema, context),
126
+ }),
127
+ string: (schema, context) => ({
128
+ type: 'string',
129
+ ...allExtensionsTransformer(schema, context),
130
+ }),
131
+ number: (schema, context) => ({
132
+ type: 'number',
133
+ ...allExtensionsTransformer(schema, context),
134
+ }),
135
+ boolean: (schema, context) => ({ type: 'boolean', ...allExtensionsTransformer(schema, context) }),
136
+ object: (schema, context) => ({
137
+ type: 'object',
138
+ ...allExtensionsTransformer(schema, context),
139
+ }),
140
+ integer: (schema, context) => ({
141
+ type: 'integer',
142
+ ...allExtensionsTransformer(schema, context),
143
+ }),
144
+ array: (schema, context) => ({
145
+ type: 'array',
146
+ ...allExtensionsTransformer(schema, context),
147
+ }),
148
+ combined: (schema, context) => ({
149
+ ...allExtensionsTransformer(schema, context),
150
+ }),
151
+ 'multi-type': (schema, context) => ({
152
+ type: schema.type as string[],
153
+ ...allExtensionsTransformer(schema, context),
154
+ }),
155
+ null: () => ({ type: 'null' }),
156
+ unknown: () => ({}),
157
+ };
158
+
159
+ function resolveDescriminatorMapping(context: OpenApiTransformerContext, schema: ApiSchema) {
160
+ const discriminator = schema.$src.component.discriminator;
161
+ if (discriminator && typeof discriminator === 'object' && discriminator.mapping) {
162
+ for (const key of Object.keys(discriminator.mapping)) {
163
+ const mappedSchemaRef = discriminator.mapping[key];
164
+ const mappedSchema = transformSchema(context, mappedSchemaRef);
165
+ if (schema.discriminator) {
166
+ schema.discriminator.mapping[key] = mappedSchema;
167
+ const hasInheritedSchema = mappedSchema.inheritedSchemas.some((x) => x.id === schema.id);
168
+ if (!hasInheritedSchema) {
169
+ mappedSchema.inheritedSchemas.push(
170
+ schema as ApiSchema & { discriminator: NonNullable<ApiSchema['discriminator']> },
171
+ );
172
+ }
173
+ }
174
+ }
175
+ }
176
+
177
+ if (schema.kind === 'combined' && schema.allOf) {
178
+ for (const s of schema.allOf) {
179
+ const base = findDiscriminatedSchema(s);
180
+ if (!base) continue;
181
+ if (base.discriminator && base.discriminator.propertyName) {
182
+ const origDiscriminator = base.$src.component.discriminator;
183
+ if (
184
+ origDiscriminator &&
185
+ typeof origDiscriminator === 'object' &&
186
+ origDiscriminator.mapping &&
187
+ Object.values(origDiscriminator.mapping).some(
188
+ (x) => x.$src.file === schema.$src.file && x.$src.path === schema.$src.path,
189
+ )
190
+ ) {
191
+ continue;
192
+ }
193
+
194
+ const mappingKey = schema.isNameGenerated ? null : schema.name;
195
+ if (!mappingKey) {
196
+ continue;
197
+ }
198
+
199
+ base.discriminator.mapping[mappingKey] = schema;
200
+ const hasInheritedSchema = schema.inheritedSchemas.some((x) => x.id === base.id);
201
+ if (!hasInheritedSchema) {
202
+ schema.inheritedSchemas.push(base as ApiSchema & { discriminator: NonNullable<ApiSchema['discriminator']> });
203
+ }
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ function findDiscriminatedSchema(
210
+ schema: ApiSchema,
211
+ ): (ApiSchema & { discriminator: NonNullable<ApiSchema['discriminator']> }) | null {
212
+ const origDiscriminator = schema.$src.originalComponent.discriminator;
213
+ if (origDiscriminator && (typeof origDiscriminator === 'string' || origDiscriminator.propertyName)) {
214
+ return schema as ApiSchema & { discriminator: NonNullable<ApiSchema['discriminator']> };
215
+ }
216
+ return schema.$ref ? findDiscriminatedSchema(schema.$ref) : null;
217
+ }
@@ -0,0 +1,53 @@
1
+ import type { OpenApiCollectorData } from '../collect/types.js';
2
+ import type { ApiData } from './api-types.js';
3
+ import { IdGenerator } from './helpers.js';
4
+ import { transformDocument } from './transform-document.js';
5
+ import { transformEndpoint } from './transform-endpoint.js';
6
+ import { transformSchema } from './transform-schema.js';
7
+ import {
8
+ defaultOpenApiTransformerOptions,
9
+ type OpenApiTransformerContext,
10
+ type OpenApiTransformerOptions,
11
+ } from './types.js';
12
+
13
+ export function transformOpenApi(data: OpenApiCollectorData, options?: Partial<OpenApiTransformerOptions>): ApiData {
14
+ const context: OpenApiTransformerContext = {
15
+ config: { ...defaultOpenApiTransformerOptions, ...options },
16
+
17
+ idGenerator: new IdGenerator(),
18
+ input: data,
19
+ incompleteSchemas: new Map(),
20
+ paths: new Map(),
21
+ services: new Map(),
22
+ endpoints: new Map(),
23
+ schemas: new Map(),
24
+
25
+ transformed: {
26
+ services: new Map(),
27
+ paths: new Map(),
28
+ parameters: new Map(),
29
+ requestBodies: new Map(),
30
+ responses: new Map(),
31
+ content: new Map(),
32
+ headers: new Map(),
33
+ schemas: new Map(),
34
+ },
35
+ };
36
+
37
+ for (const document of data.documents) {
38
+ transformDocument(context, document);
39
+ }
40
+ for (const schema of context.input.schemas.values()) {
41
+ transformSchema(context, schema);
42
+ }
43
+ for (const endpoint of context.input.endpoints.values()) {
44
+ transformEndpoint(context, endpoint);
45
+ }
46
+
47
+ return {
48
+ documents: context.input.documents,
49
+ services: Array.from(context.services.values()),
50
+ endpoints: Array.from(context.endpoints.values()),
51
+ schemas: Array.from(context.schemas.values()),
52
+ };
53
+ }
@@ -0,0 +1,52 @@
1
+ import type { OpenApiCollectorData } from '../collect/types.js';
2
+ import type { ArrayItem } from '../utils/type.utils.js';
3
+ import type {
4
+ ApiContent,
5
+ ApiData,
6
+ ApiHeader,
7
+ ApiParameter,
8
+ ApiPath,
9
+ ApiRequestBody,
10
+ ApiResponse,
11
+ ApiSchema,
12
+ ApiSchemaExtensions,
13
+ ApiSchemaKind,
14
+ ApiService,
15
+ } from './api-types.js';
16
+ import type { IdGenerator } from './helpers.js';
17
+
18
+ export type OpenApiTransformerOptions = {
19
+ /**
20
+ * Determines how schemas without a `type` property should be treated.
21
+ * - `keep-unknown`: Keep the schema as-is.
22
+ * - `object-if-properties`: Treat the schema as an object schema only if it has properties.
23
+ * - `always-object`: Treat the schema as an object schema.
24
+ * @default 'object-if-properties'
25
+ */
26
+ unknownTypeBehavior: 'keep-unknown' | 'object-if-properties' | 'always-object';
27
+ };
28
+
29
+ export const defaultOpenApiTransformerOptions: OpenApiTransformerOptions = {
30
+ unknownTypeBehavior: 'object-if-properties',
31
+ };
32
+
33
+ export type IncompleteApiSchema = Omit<ApiSchema, Exclude<keyof ApiSchemaExtensions<ApiSchemaKind>, 'kind'>>;
34
+ export type OpenApiTransformerContext = {
35
+ config: OpenApiTransformerOptions;
36
+
37
+ idGenerator: IdGenerator;
38
+ input: OpenApiCollectorData;
39
+ incompleteSchemas: Map<string, IncompleteApiSchema>;
40
+ paths: Map<string, ApiPath>;
41
+
42
+ transformed: {
43
+ services: Map<string, ApiService>;
44
+ paths: Map<string, ApiPath>;
45
+ parameters: Map<string, ApiParameter>;
46
+ requestBodies: Map<string, ApiRequestBody>;
47
+ responses: Map<string, ApiResponse>;
48
+ content: Map<string, ApiContent>;
49
+ headers: Map<string, ApiHeader>;
50
+ schemas: Map<string, ApiSchema>;
51
+ };
52
+ } & { [K in Exclude<keyof ApiData, 'documents'>]: Map<string, ArrayItem<ApiData[K]>> };
@@ -0,0 +1,58 @@
1
+ // deno-lint-ignore-file
2
+ import type { EmptyConstructor, FunctionNames } from './type.utils.js';
3
+
4
+ type MatchingFunctionNames<T, TAction> = {
5
+ [K in keyof T]: T[K] extends TAction ? K : never;
6
+ }[keyof T];
7
+
8
+ type CtorActionRunner<T, TAction> = {
9
+ kind: 'ctor';
10
+ factory: EmptyConstructor<T>;
11
+ functionName: MatchingFunctionNames<T, TAction>;
12
+ };
13
+
14
+ type FnActionRunner<TAction> = {
15
+ kind: 'fn';
16
+ action: TAction;
17
+ };
18
+
19
+ type ValueActionRunner<T, TAction> = {
20
+ kind: 'value';
21
+ value: T;
22
+ functionName: MatchingFunctionNames<T, TAction>;
23
+ };
24
+
25
+ type ActionRunner<TAction> = CtorActionRunner<any, TAction> | FnActionRunner<TAction> | ValueActionRunner<any, TAction>;
26
+
27
+ export class ActionProvider<TAction extends (...args: any[]) => any> {
28
+ constructor(private readonly _runner: ActionRunner<TAction>) {}
29
+
30
+ public run(...args: Parameters<TAction>): ReturnType<TAction> {
31
+ switch (this._runner.kind) {
32
+ case 'ctor':
33
+ return new this._runner.factory()[this._runner.functionName as any](...args);
34
+ case 'fn':
35
+ return this._runner.action(...args);
36
+ case 'value':
37
+ return this._runner.value[this._runner.functionName as any](...args);
38
+ }
39
+ }
40
+
41
+ public static fromType<T, TName extends FunctionNames<T>>(
42
+ type: EmptyConstructor<T>,
43
+ functionName: TName,
44
+ ): ActionProvider<T[TName] extends (...args: any[]) => any ? T[TName] : never> {
45
+ return new ActionProvider({ kind: 'ctor', factory: type, functionName: functionName as any });
46
+ }
47
+
48
+ public static fromFn<TAction extends (...args: any[]) => any>(fn: TAction): ActionProvider<TAction> {
49
+ return new ActionProvider({ kind: 'fn', action: fn });
50
+ }
51
+
52
+ public static fromValue<T, TName extends FunctionNames<T>>(
53
+ value: T,
54
+ functionName: TName,
55
+ ): ActionProvider<T[TName] extends (...args: any[]) => any ? T[TName] : never> {
56
+ return new ActionProvider({ kind: 'value', value, functionName: functionName as any });
57
+ }
58
+ }
@@ -0,0 +1,48 @@
1
+ import { dirname, relative } from 'node:path';
2
+ import process from 'node:process';
3
+ import type { ApiComponent, ApiData } from '../transform/api-types.js';
4
+
5
+ const DOCUMENTS_BASE_PATH = Symbol('documentsBasePath');
6
+
7
+ type ApiDataWithBasePath = ApiData & {
8
+ [DOCUMENTS_BASE_PATH]?: string;
9
+ };
10
+
11
+ export function getSourceDisplayName<T>(data: ApiData, component: ApiComponent<T>): string {
12
+ let basePath: string | undefined = (data as ApiDataWithBasePath)[DOCUMENTS_BASE_PATH];
13
+ if (basePath === undefined) {
14
+ const allPaths = data.documents.map((doc) => doc.$src.file);
15
+ basePath = getCommonBasePath(allPaths);
16
+ (data as ApiDataWithBasePath)[DOCUMENTS_BASE_PATH] = basePath;
17
+ }
18
+
19
+ const relativePath = relative(basePath, component.$src.file);
20
+ return `${relativePath}#${component.$src.path}`;
21
+ }
22
+
23
+ function getCommonBasePath(paths: string[]): string {
24
+ if (paths.length === 0) return '';
25
+ if (paths.length === 1) return dirname(paths[0]);
26
+
27
+ const splitPaths = paths.map((p) => normalizePath(p).split('/'));
28
+ const minLength = Math.min(...splitPaths.map((parts) => parts.length));
29
+ const commonParts: string[] = [];
30
+
31
+ for (let i = 0; i < minLength; i++) {
32
+ const part = splitPaths[0][i];
33
+ if (splitPaths.every((parts) => parts[i] === part)) {
34
+ commonParts.push(part);
35
+ } else {
36
+ break;
37
+ }
38
+ }
39
+
40
+ return commonParts.join('/');
41
+ }
42
+
43
+ function normalizePath(path: string): string {
44
+ if (process.platform === 'win32') {
45
+ return path.replace(/\\/g, '/');
46
+ }
47
+ return path;
48
+ }
@@ -0,0 +1,7 @@
1
+ export function removeItems<T>(array: T[], predicate: (item: T) => boolean): void {
2
+ for (let i = array.length - 1; i >= 0; i--) {
3
+ if (predicate(array[i])) {
4
+ array.splice(i, 1);
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,51 @@
1
+ import * as dntShim from "../../_dnt.shims.js";
2
+ import { basename, resolve } from 'node:path';
3
+ import { fileURLToPath, URL } from 'node:url';
4
+
5
+
6
+ import fs from 'fs-extra';
7
+
8
+ export class AssetManager {
9
+ private readonly _assetsBaseUrl: URL;
10
+
11
+ constructor(assetsBaseUrl: URL) {
12
+ this._assetsBaseUrl = assetsBaseUrl;
13
+ }
14
+
15
+ public async getAssetFileContent(relativePath: string): Promise<string> {
16
+ if (this._assetsBaseUrl.protocol === 'file:') {
17
+ return await fs.readFile(resolve(fileURLToPath(this._assetsBaseUrl), relativePath), 'utf8');
18
+ } else {
19
+ return await this.fetchAssetFile(relativePath);
20
+ }
21
+ }
22
+
23
+ public async copyAssetFile(relativePath: string, destDir: string, logName?: string): Promise<void> {
24
+ const targetPath = resolve(destDir, basename(relativePath));
25
+ if (logName !== undefined) {
26
+ if (logName) {
27
+ console.log(`Copying ${logName} to "${targetPath}"`);
28
+ } else {
29
+ console.log(`Copying asset file "${relativePath}" to "${targetPath}"`);
30
+ }
31
+ }
32
+ if (this._assetsBaseUrl.protocol === 'file:') {
33
+ const srcPath = resolve(fileURLToPath(this._assetsBaseUrl), relativePath);
34
+ await fs.copyFile(srcPath, targetPath);
35
+ } else {
36
+ const fileContent = await this.fetchAssetFile(relativePath);
37
+ await fs.outputFile(targetPath, fileContent);
38
+ }
39
+ }
40
+
41
+ private async fetchAssetFile(relativePath: string): Promise<string> {
42
+ const url = new URL(relativePath, this._assetsBaseUrl);
43
+ const response = await dntShim.fetch(url);
44
+ if (!response.ok) {
45
+ throw new Error(
46
+ `Failed to fetch asset file "${relativePath}" from "${url.href}": ${response.statusText} (${response.status})`,
47
+ );
48
+ }
49
+ return await response.text();
50
+ }
51
+ }
@@ -0,0 +1,40 @@
1
+ // deno-lint-ignore-file no-explicit-any
2
+ export function classifyArray<TItem, TClassification extends Record<string, abstract new (...args: any[]) => any>>(
3
+ items: Iterable<TItem>,
4
+ classification: TClassification,
5
+ ): { [P in keyof TClassification]: InstanceType<TClassification[P]>[] } & {
6
+ rest: Exclude<TItem, InstanceType<TClassification[string]>>[];
7
+ } {
8
+ const result = { rest: [] } as { [P in keyof TClassification]: InstanceType<TClassification[P]>[] } & {
9
+ rest: Exclude<TItem, InstanceType<TClassification[string]>>[];
10
+ };
11
+
12
+ for (const key in classification) {
13
+ (result as any)[key] = [];
14
+ }
15
+
16
+ for (const item of items) {
17
+ let classified = false;
18
+ for (const key in classification) {
19
+ const type = classification[key];
20
+ if (item instanceof type) {
21
+ result[key].push(item as any);
22
+ classified = true;
23
+ break;
24
+ }
25
+ }
26
+ if (!classified) {
27
+ result.rest.push(item as any);
28
+ }
29
+ }
30
+
31
+ return result;
32
+ }
33
+
34
+ export function isInstanceOf<T>(item: unknown, type: abstract new (...args: any[]) => T): item is T {
35
+ return item instanceof type;
36
+ }
37
+
38
+ export function getIsInstanceOf<T>(type: abstract new (...args: any[]) => T): (item: unknown) => item is T {
39
+ return (item): item is T => item instanceof type;
40
+ }
@@ -0,0 +1,86 @@
1
+ import type { SingleOrMultiple } from './type.utils.js';
2
+
3
+ export function isNullish(value: unknown): value is null | undefined {
4
+ return value === null || value === undefined;
5
+ }
6
+
7
+ export function notNullish<T>(value: T): value is NonNullable<T> {
8
+ return !isNullish(value);
9
+ }
10
+
11
+ export function concatSingleOrMultiple<T>(...values: SingleOrMultiple<T>[]): SingleOrMultiple<T> {
12
+ if (values.length === 1) {
13
+ return values[0];
14
+ }
15
+ const result: T[] = [];
16
+ for (const value of values) {
17
+ if (Array.isArray(value)) {
18
+ result.push(...value);
19
+ } else {
20
+ result.push(value);
21
+ }
22
+ }
23
+ return result;
24
+ }
25
+
26
+ export function isIterable(value: unknown): value is Iterable<unknown> {
27
+ return typeof value === 'object' && value !== null && Symbol.iterator in value;
28
+ }
29
+
30
+ export function toArray<T>(value: T | T[] | Iterable<T>): T[] {
31
+ if (Array.isArray(value)) {
32
+ return value;
33
+ }
34
+ if (isIterable(value)) {
35
+ return Array.from(value);
36
+ }
37
+ return [value];
38
+ }
39
+
40
+ export function modify<T>(value: T, callback: (value: NonNullable<T>) => void): T {
41
+ if (notNullish(value)) {
42
+ callback(value);
43
+ }
44
+ return value;
45
+ }
46
+
47
+ export function run<T>(value: T, callback: (value: T) => void): T {
48
+ callback(value);
49
+ return value;
50
+ }
51
+
52
+ export function transform<T, U>(value: T, callback: (value: T) => U): U {
53
+ return callback(value);
54
+ }
55
+
56
+ type IterableItem<T> = T extends Iterable<infer U> ? U : never;
57
+ export function modifyEach<T extends Iterable<unknown>>(
58
+ values: T,
59
+ callback: (value: NonNullable<IterableItem<T>>) => void,
60
+ ): T {
61
+ for (const value of values) {
62
+ if (notNullish(value)) {
63
+ callback(value as NonNullable<IterableItem<T>>);
64
+ }
65
+ }
66
+ return values;
67
+ }
68
+
69
+ export function runEach<T extends Iterable<unknown>>(values: T, callback: (value: IterableItem<T>) => void): T {
70
+ for (const value of values) {
71
+ callback(value as IterableItem<T>);
72
+ }
73
+ return values;
74
+ }
75
+
76
+ export function transformEach<T, U>(values: Iterable<T>, callback: (value: T) => U): U[] {
77
+ const result: U[] = [];
78
+ for (const value of values) {
79
+ result.push(callback(value));
80
+ }
81
+ return result;
82
+ }
83
+
84
+ export function asVoid(_: unknown): void {
85
+ // Do nothing
86
+ }
@@ -0,0 +1,7 @@
1
+ export type Condition<T = never> = boolean | ([T] extends [never] ? () => boolean : (ctx: T) => boolean);
2
+ export function evalCondition(condition: Condition<never>): boolean;
3
+ export function evalCondition<T>(condition: Condition<T>, ctx: T): boolean;
4
+ export function evalCondition<T>(condition: Condition<T>, ctx?: T): boolean {
5
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
6
+ return typeof condition === 'function' ? condition(ctx!) : condition;
7
+ }
@@ -0,0 +1,10 @@
1
+ import type { ApiEndpoint } from '../transform/api-types.js';
2
+
3
+ export function getEndpointUrlPreview(endpoint: ApiEndpoint): string {
4
+ let url = endpoint.path;
5
+ const queryParams = endpoint.parameters.filter((p) => p.target === 'query');
6
+ if (queryParams.length > 0) {
7
+ url += '?' + queryParams.map((p) => (p.required ? `${p.name}={${p.name}}` : `[${p.name}={${p.name}}]`)).join('&');
8
+ }
9
+ return url;
10
+ }
@@ -0,0 +1,3 @@
1
+ export function throwExp(message: string): never {
2
+ throw new Error(message);
3
+ }
@@ -0,0 +1,42 @@
1
+ import type { Constructor } from './type.utils.js';
2
+
3
+ type FactoryProvider<T, TArgs extends unknown[]> =
4
+ | {
5
+ kind: 'ctor';
6
+ factory: Constructor<T, TArgs>;
7
+ }
8
+ | {
9
+ kind: 'fn';
10
+ factory: (...args: TArgs) => T;
11
+ }
12
+ | {
13
+ kind: 'value';
14
+ factory: T;
15
+ };
16
+
17
+ export class Factory<T, TArgs extends unknown[]> {
18
+ constructor(private readonly _provider: FactoryProvider<T, TArgs>) {}
19
+
20
+ public create(...args: TArgs): T {
21
+ switch (this._provider.kind) {
22
+ case 'ctor':
23
+ return new this._provider.factory(...args);
24
+ case 'fn':
25
+ return this._provider.factory(...args);
26
+ case 'value':
27
+ return this._provider.factory;
28
+ }
29
+ }
30
+
31
+ public static fromType<T, TArgs extends unknown[]>(type: Constructor<T, TArgs>): Factory<T, TArgs> {
32
+ return new Factory({ kind: 'ctor', factory: type });
33
+ }
34
+
35
+ public static fromFn<T, TArgs extends unknown[]>(fn: (...args: TArgs) => T): Factory<T, TArgs> {
36
+ return new Factory({ kind: 'fn', factory: fn });
37
+ }
38
+
39
+ public static fromValue<T>(value: T): Factory<T, []> {
40
+ return new Factory({ kind: 'value', factory: value });
41
+ }
42
+ }