@zimic/http 0.0.1-canary.2

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 (61) hide show
  1. package/LICENSE.md +16 -0
  2. package/README.md +230 -0
  3. package/dist/chunk-VHQRAQPQ.mjs +1371 -0
  4. package/dist/chunk-VHQRAQPQ.mjs.map +1 -0
  5. package/dist/chunk-VUDGONB5.js +1382 -0
  6. package/dist/chunk-VUDGONB5.js.map +1 -0
  7. package/dist/cli.js +116 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/cli.mjs +109 -0
  10. package/dist/cli.mjs.map +1 -0
  11. package/dist/index.d.ts +1306 -0
  12. package/dist/index.js +544 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/index.mjs +537 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/typegen.d.ts +86 -0
  17. package/dist/typegen.js +12 -0
  18. package/dist/typegen.js.map +1 -0
  19. package/dist/typegen.mjs +3 -0
  20. package/dist/typegen.mjs.map +1 -0
  21. package/index.d.ts +1 -0
  22. package/package.json +110 -0
  23. package/src/cli/cli.ts +92 -0
  24. package/src/cli/index.ts +4 -0
  25. package/src/cli/typegen/openapi.ts +24 -0
  26. package/src/formData/HttpFormData.ts +300 -0
  27. package/src/formData/types.ts +110 -0
  28. package/src/headers/HttpHeaders.ts +217 -0
  29. package/src/headers/types.ts +65 -0
  30. package/src/index.ts +55 -0
  31. package/src/pathParams/types.ts +67 -0
  32. package/src/searchParams/HttpSearchParams.ts +258 -0
  33. package/src/searchParams/types.ts +133 -0
  34. package/src/typegen/index.ts +12 -0
  35. package/src/typegen/namespace/TypegenNamespace.ts +18 -0
  36. package/src/typegen/openapi/generate.ts +168 -0
  37. package/src/typegen/openapi/transform/components.ts +481 -0
  38. package/src/typegen/openapi/transform/context.ts +67 -0
  39. package/src/typegen/openapi/transform/filters.ts +71 -0
  40. package/src/typegen/openapi/transform/imports.ts +15 -0
  41. package/src/typegen/openapi/transform/io.ts +86 -0
  42. package/src/typegen/openapi/transform/methods.ts +803 -0
  43. package/src/typegen/openapi/transform/operations.ts +120 -0
  44. package/src/typegen/openapi/transform/paths.ts +119 -0
  45. package/src/typegen/openapi/utils/types.ts +45 -0
  46. package/src/types/arrays.d.ts +4 -0
  47. package/src/types/json.ts +89 -0
  48. package/src/types/objects.d.ts +14 -0
  49. package/src/types/requests.ts +96 -0
  50. package/src/types/schema.ts +834 -0
  51. package/src/types/strings.d.ts +9 -0
  52. package/src/types/utils.ts +64 -0
  53. package/src/utils/console.ts +7 -0
  54. package/src/utils/data.ts +13 -0
  55. package/src/utils/files.ts +28 -0
  56. package/src/utils/imports.ts +12 -0
  57. package/src/utils/prettier.ts +13 -0
  58. package/src/utils/strings.ts +3 -0
  59. package/src/utils/time.ts +25 -0
  60. package/src/utils/urls.ts +52 -0
  61. package/typegen.d.ts +1 -0
@@ -0,0 +1,120 @@
1
+ import ts from 'typescript';
2
+
3
+ import { Override } from '@/types/utils';
4
+ import { isDefined } from '@/utils/data';
5
+
6
+ import { TypeTransformContext } from './context';
7
+ import { normalizeTypeLiteralMethodType } from './methods';
8
+
9
+ export function createOperationsIdentifierText(serviceName: string) {
10
+ return `${serviceName}Operations`;
11
+ }
12
+
13
+ export function createOperationsIdentifier(serviceName: string) {
14
+ return ts.factory.createIdentifier(createOperationsIdentifierText(serviceName));
15
+ }
16
+
17
+ type OperationsDeclaration = ts.InterfaceDeclaration;
18
+
19
+ export function isOperationsDeclaration(node: ts.Node | undefined): node is OperationsDeclaration {
20
+ return node !== undefined && ts.isInterfaceDeclaration(node) && node.name.text === 'operations';
21
+ }
22
+
23
+ type Operation = Override<
24
+ ts.PropertySignature,
25
+ {
26
+ type: ts.TypeLiteralNode;
27
+ name: ts.Identifier | ts.StringLiteral;
28
+ }
29
+ >;
30
+
31
+ function isOperation(node: ts.Node): node is Operation {
32
+ return (
33
+ ts.isPropertySignature(node) &&
34
+ (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) &&
35
+ node.type !== undefined &&
36
+ ts.isTypeLiteralNode(node.type)
37
+ );
38
+ }
39
+
40
+ function wrapOperationType(type: ts.TypeLiteralNode, context: TypeTransformContext) {
41
+ context.typeImports.http.add('HttpSchema');
42
+
43
+ const httpSchemaMethodWrapper = ts.factory.createQualifiedName(
44
+ ts.factory.createIdentifier('HttpSchema'),
45
+ ts.factory.createIdentifier('Method'),
46
+ );
47
+ return ts.factory.createTypeReferenceNode(httpSchemaMethodWrapper, [type]);
48
+ }
49
+
50
+ function normalizeOperation(operation: ts.TypeElement, context: TypeTransformContext) {
51
+ /* istanbul ignore if -- @preserve
52
+ * Operation members are always expected to be an operation. */
53
+ if (!isOperation(operation)) {
54
+ return undefined;
55
+ }
56
+
57
+ const newType = normalizeTypeLiteralMethodType(operation.type, context);
58
+
59
+ return ts.factory.updatePropertySignature(
60
+ operation,
61
+ operation.modifiers,
62
+ operation.name,
63
+ operation.questionToken,
64
+ wrapOperationType(newType, context),
65
+ );
66
+ }
67
+
68
+ export function normalizeOperations(operations: OperationsDeclaration, context: TypeTransformContext) {
69
+ const newIdentifier = createOperationsIdentifier(context.serviceName);
70
+
71
+ const newMembers = operations.members.map((operation) => normalizeOperation(operation, context)).filter(isDefined);
72
+
73
+ return ts.factory.updateInterfaceDeclaration(
74
+ operations,
75
+ operations.modifiers,
76
+ newIdentifier,
77
+ operations.typeParameters,
78
+ operations.heritageClauses,
79
+ newMembers,
80
+ );
81
+ }
82
+
83
+ function removeOperationIfUnreferenced(operation: ts.TypeElement, context: TypeTransformContext) {
84
+ /* istanbul ignore if -- @preserve
85
+ * Operation members are always expected to be an operation. */
86
+ if (!isOperation(operation)) {
87
+ return undefined;
88
+ }
89
+
90
+ const operationName = operation.name.text;
91
+ const isReferenced = context.referencedTypes.operations.has(operationName);
92
+
93
+ if (isReferenced) {
94
+ context.referencedTypes.operations.delete(operationName);
95
+ return operation;
96
+ }
97
+
98
+ return undefined;
99
+ }
100
+
101
+ export function removeUnreferencedOperations(operations: OperationsDeclaration, context: TypeTransformContext) {
102
+ const newMembers = operations.members
103
+ .map((operation) => removeOperationIfUnreferenced(operation, context))
104
+ .filter(isDefined);
105
+
106
+ context.referencedTypes.operations.clear();
107
+
108
+ if (newMembers.length === 0) {
109
+ return undefined;
110
+ }
111
+
112
+ return ts.factory.updateInterfaceDeclaration(
113
+ operations,
114
+ operations.modifiers,
115
+ operations.name,
116
+ operations.typeParameters,
117
+ operations.heritageClauses,
118
+ newMembers,
119
+ );
120
+ }
@@ -0,0 +1,119 @@
1
+ import ts from 'typescript';
2
+
3
+ import { Override } from '@/types/utils';
4
+ import { isDefined } from '@/utils/data';
5
+
6
+ import { renameComponentReferences } from './components';
7
+ import { TypeTransformContext } from './context';
8
+ import { normalizeMethod } from './methods';
9
+
10
+ export function createPathsIdentifier(serviceName: string) {
11
+ return ts.factory.createIdentifier(`${serviceName}Schema`);
12
+ }
13
+
14
+ type PathsDeclaration = ts.InterfaceDeclaration | ts.TypeAliasDeclaration;
15
+
16
+ export function isPathsDeclaration(node: ts.Node | undefined): node is PathsDeclaration {
17
+ return (
18
+ node !== undefined &&
19
+ (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) &&
20
+ node.name.text === 'paths'
21
+ );
22
+ }
23
+
24
+ type Path = Override<
25
+ ts.PropertySignature,
26
+ {
27
+ type: ts.TypeLiteralNode | ts.IndexedAccessTypeNode;
28
+ name: ts.Identifier | ts.StringLiteral;
29
+ }
30
+ >;
31
+
32
+ function isPath(node: ts.TypeElement): node is Path {
33
+ return (
34
+ ts.isPropertySignature(node) &&
35
+ (ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) &&
36
+ node.type !== undefined &&
37
+ (ts.isTypeLiteralNode(node.type) || ts.isIndexedAccessTypeNode(node.type))
38
+ );
39
+ }
40
+
41
+ function normalizePathNameWithParameters(pathName: string) {
42
+ return pathName.replace(/{([^}]+)}/g, ':$1');
43
+ }
44
+
45
+ function wrapComponentPathType(type: ts.TypeNode, context: TypeTransformContext) {
46
+ context.typeImports.http.add('HttpSchema');
47
+
48
+ const httpSchemaMethodsWrapper = ts.factory.createQualifiedName(
49
+ ts.factory.createIdentifier('HttpSchema'),
50
+ ts.factory.createIdentifier('Methods'),
51
+ );
52
+ return ts.factory.createTypeReferenceNode(httpSchemaMethodsWrapper, [type]);
53
+ }
54
+
55
+ export function normalizePath(
56
+ path: ts.TypeElement,
57
+ context: TypeTransformContext,
58
+ options: { isComponent?: boolean } = {},
59
+ ) {
60
+ const { isComponent = false } = options;
61
+
62
+ /* istanbul ignore if -- @preserve
63
+ * Path members are always expected to be a path. */
64
+ if (!isPath(path)) {
65
+ return undefined;
66
+ }
67
+
68
+ const newPathName = isComponent ? path.name.text : normalizePathNameWithParameters(path.name.text);
69
+ const newIdentifier = isComponent ? path.name : ts.factory.createStringLiteral(newPathName);
70
+
71
+ let newType: ts.TypeNode;
72
+
73
+ if (ts.isTypeLiteralNode(path.type)) {
74
+ const newMethods = path.type.members
75
+ .map((method) => normalizeMethod(method, context, { pathName: newPathName }))
76
+ .filter(isDefined);
77
+
78
+ if (newMethods.length === 0) {
79
+ return undefined;
80
+ }
81
+
82
+ newType = ts.factory.updateTypeLiteralNode(path.type, ts.factory.createNodeArray(newMethods));
83
+ } else {
84
+ newType = renameComponentReferences(path.type, context);
85
+ }
86
+
87
+ return ts.factory.updatePropertySignature(
88
+ path,
89
+ path.modifiers,
90
+ newIdentifier,
91
+ path.questionToken,
92
+ isComponent ? wrapComponentPathType(newType, context) : newType,
93
+ );
94
+ }
95
+
96
+ function wrapPathsType(type: ts.TypeLiteralNode, context: TypeTransformContext) {
97
+ context.typeImports.http.add('HttpSchema');
98
+
99
+ const httpSchemaPathsWrapper = ts.factory.createIdentifier('HttpSchema');
100
+ return ts.factory.createTypeReferenceNode(httpSchemaPathsWrapper, [type]);
101
+ }
102
+
103
+ export function normalizePaths(pathsOrTypeAlias: PathsDeclaration, context: TypeTransformContext) {
104
+ const newIdentifier = createPathsIdentifier(context.serviceName);
105
+
106
+ const paths = ts.isTypeAliasDeclaration(pathsOrTypeAlias)
107
+ ? ts.factory.createInterfaceDeclaration(pathsOrTypeAlias.modifiers, pathsOrTypeAlias.name, undefined, undefined, [])
108
+ : pathsOrTypeAlias;
109
+
110
+ const newMembers = paths.members.map((path) => normalizePath(path, context)).filter(isDefined);
111
+ const newType = ts.factory.createTypeLiteralNode(newMembers);
112
+
113
+ return ts.factory.createTypeAliasDeclaration(
114
+ paths.modifiers,
115
+ newIdentifier,
116
+ paths.typeParameters,
117
+ wrapPathsType(newType, context),
118
+ );
119
+ }
@@ -0,0 +1,45 @@
1
+ import ts from 'typescript';
2
+
3
+ export function isNeverType(type: ts.TypeNode) {
4
+ return type.kind === ts.SyntaxKind.NeverKeyword;
5
+ }
6
+
7
+ export function isUnknownType(type: ts.TypeNode) {
8
+ return type.kind === ts.SyntaxKind.UnknownKeyword;
9
+ }
10
+
11
+ export function isNumericType(type: ts.TypeNode) {
12
+ return type.kind === ts.SyntaxKind.NumberKeyword;
13
+ }
14
+
15
+ export function isBooleanType(type: ts.TypeNode) {
16
+ return type.kind === ts.SyntaxKind.BooleanKeyword;
17
+ }
18
+
19
+ export function isNullType(type: ts.TypeNode | ts.LiteralTypeNode['literal']) {
20
+ return type.kind === ts.SyntaxKind.NullKeyword;
21
+ }
22
+
23
+ export function createBlobType() {
24
+ return ts.factory.createTypeReferenceNode('Blob');
25
+ }
26
+
27
+ export function createNullType() {
28
+ return ts.factory.createLiteralTypeNode(ts.factory.createNull());
29
+ }
30
+
31
+ export function createImportSpecifier(importName: string) {
32
+ return ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(importName));
33
+ }
34
+
35
+ export function createImportDeclaration(
36
+ importSpecifiers: ts.ImportSpecifier[],
37
+ moduleName: string,
38
+ options: { typeOnly: boolean },
39
+ ) {
40
+ return ts.factory.createImportDeclaration(
41
+ undefined,
42
+ ts.factory.createImportClause(options.typeOnly, undefined, ts.factory.createNamedImports(importSpecifiers)),
43
+ ts.factory.createStringLiteral(moduleName),
44
+ );
45
+ }
@@ -0,0 +1,4 @@
1
+ interface ArrayConstructor {
2
+ // eslint-disable-next-line @typescript-eslint/method-signature-style
3
+ isArray<Item>(value: unknown): value is Item[];
4
+ }
@@ -0,0 +1,89 @@
1
+ type JSON = { [key: string]: JSON } | JSON[] | string | number | boolean | null | undefined;
2
+
3
+ namespace JSON {
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ export type Loose = Record<string, any> | Loose[] | string | number | boolean | null | undefined;
6
+ }
7
+
8
+ /**
9
+ * Represents or validates a type that is compatible with JSON.
10
+ *
11
+ * **IMPORTANT**: the input of `JSONValue` and all of its internal types must be declared inline or as a type aliases
12
+ * (`type`). They cannot be interfaces.
13
+ *
14
+ * @example
15
+ * import { type JSONValue } from '@zimic/http';
16
+ *
17
+ * // Can be used as a standalone type:
18
+ * const value: JSONValue = {
19
+ * name: 'example',
20
+ * tags: ['one', 'two'],
21
+ * };
22
+ *
23
+ * @example
24
+ * import { type JSONValue } from '@zimic/http';
25
+ *
26
+ * // Can be used with a type argument to validate a JSON value:
27
+ * type ValidJSON = JSONValue<{
28
+ * id: string;
29
+ * email: string;
30
+ * createdAt: string;
31
+ * }>;
32
+ *
33
+ * // This results in a type error:
34
+ * type InvalidJSON = JSONValue<{
35
+ * id: string;
36
+ * email: string;
37
+ * createdAt: Date; // `Date` is not a valid JSON value.
38
+ * save: () => Promise<void>; // Functions are not valid JSON values.
39
+ * }>;
40
+ */
41
+ export type JSONValue<Type extends JSON = JSON> = Type;
42
+
43
+ export namespace JSONValue {
44
+ /** A loose version of the JSON value type. JSON objects are not strictly typed. */
45
+ export type Loose<Type extends JSON.Loose = JSON.Loose> = Type;
46
+ }
47
+
48
+ /**
49
+ * Recursively converts a type to its JSON-serialized version. Dates are converted to strings and keys with non-JSON
50
+ * values are excluded.
51
+ *
52
+ * @example
53
+ * import { type JSONSerialized } from '@zimic/http';
54
+ *
55
+ * type SerializedUser = JSONSerialized<{
56
+ * id: string;
57
+ * email: string;
58
+ * createdAt: Date;
59
+ * save: () => Promise<void>;
60
+ * }>;
61
+ * // {
62
+ * // id: string;
63
+ * // email: string;
64
+ * // createdAt: string;
65
+ * // }
66
+ */
67
+ export type JSONSerialized<Type> = Type extends JSONValue
68
+ ? Type
69
+ : Type extends string | number | boolean | null | undefined
70
+ ? Type
71
+ : Type extends Date
72
+ ? string
73
+ : Type extends (...parameters: never[]) => unknown
74
+ ? never
75
+ : Type extends symbol
76
+ ? never
77
+ : Type extends Map<infer _Key, infer _Value>
78
+ ? Record<string, never>
79
+ : Type extends Set<infer _Value>
80
+ ? Record<string, never>
81
+ : Type extends (infer ArrayItem)[]
82
+ ? JSONSerialized<ArrayItem>[]
83
+ : Type extends object
84
+ ? {
85
+ [Key in keyof Type as [JSONSerialized<Type[Key]>] extends [never] ? never : Key]: JSONSerialized<
86
+ Type[Key]
87
+ >;
88
+ }
89
+ : never;
@@ -0,0 +1,14 @@
1
+ type ObjectKey<Type> = keyof Type & string;
2
+ type ObjectValue<Type> = Type[ObjectKey<Type>];
3
+ type ObjectEntry<Type> = [ObjectKey<Type>, ObjectValue<Type>];
4
+
5
+ interface ObjectConstructor {
6
+ // eslint-disable-next-line @typescript-eslint/method-signature-style
7
+ keys<Type>(object: Type): ObjectKey<Type>[];
8
+
9
+ // eslint-disable-next-line @typescript-eslint/method-signature-style
10
+ values<Type>(object: Type): ObjectValue<Type>[];
11
+
12
+ // eslint-disable-next-line @typescript-eslint/method-signature-style
13
+ entries<Type>(object: Type): ObjectEntry<Type>[];
14
+ }
@@ -0,0 +1,96 @@
1
+ import { JSONSerialized, JSONValue } from '@/types/json';
2
+ import { HttpStatusCode } from '@/types/schema';
3
+ import { ReplaceBy } from '@/types/utils';
4
+
5
+ import HttpFormData from '../formData/HttpFormData';
6
+ import { HttpFormDataSchema } from '../formData/types';
7
+ import HttpHeaders from '../headers/HttpHeaders';
8
+ import { HttpHeadersSchema } from '../headers/types';
9
+ import HttpSearchParams from '../searchParams/HttpSearchParams';
10
+ import { HttpSearchParamsSchema } from '../searchParams/types';
11
+
12
+ /** The body type for HTTP requests and responses. */
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ export type HttpBody = JSONValue | HttpFormData<any> | HttpSearchParams<any> | Blob | ArrayBuffer;
15
+
16
+ export namespace HttpBody {
17
+ /** A loose version of the HTTP body type. JSON values are not strictly typed. */
18
+ export type Loose = ReplaceBy<HttpBody, JSONValue, JSONValue.Loose>;
19
+
20
+ /** Convert a possibly loose HTTP body to be strictly typed. JSON values are serialized to their strict form. */
21
+ export type ConvertToStrict<Type> = Type extends Exclude<HttpBody, JSONValue> ? Type : JSONSerialized<Type>;
22
+ }
23
+
24
+ /**
25
+ * An HTTP headers object with a strictly-typed schema. Fully compatible with the built-in
26
+ * {@link https://developer.mozilla.org/docs/Web/API/Headers `Headers`} class.
27
+ */
28
+ export type StrictHeaders<Schema extends HttpHeadersSchema = HttpHeadersSchema> = Pick<
29
+ HttpHeaders<Schema>,
30
+ keyof Headers
31
+ >;
32
+
33
+ /**
34
+ * An HTTP search params object with a strictly-typed schema. Fully compatible with the built-in
35
+ * {@link https://developer.mozilla.org/docs/Web/API/URLSearchParams `URLSearchParams`} class.
36
+ */
37
+ export type StrictURLSearchParams<Schema extends HttpSearchParamsSchema = HttpSearchParamsSchema> = Pick<
38
+ HttpSearchParams<Schema>,
39
+ keyof URLSearchParams
40
+ >;
41
+
42
+ /**
43
+ * An HTTP form data object with a strictly-typed schema. Fully compatible with the built-in
44
+ * {@link https://developer.mozilla.org/docs/Web/API/FormData `FormData`} class.
45
+ */
46
+ export type StrictFormData<Schema extends HttpFormDataSchema = HttpFormDataSchema> = Pick<
47
+ HttpFormData<Schema>,
48
+ keyof FormData
49
+ >;
50
+
51
+ /**
52
+ * An HTTP request with a strictly-typed JSON body. Fully compatible with the built-in
53
+ * {@link https://developer.mozilla.org/docs/Web/API/Request `Request`} class.
54
+ */
55
+ export interface HttpRequest<
56
+ StrictBody extends HttpBody.Loose = HttpBody,
57
+ StrictHeadersSchema extends HttpHeadersSchema = HttpHeadersSchema,
58
+ > extends Request {
59
+ headers: StrictHeaders<StrictHeadersSchema>;
60
+ text: () => Promise<StrictBody extends string ? StrictBody : string>;
61
+ json: () => Promise<StrictBody extends string | Exclude<HttpBody, JSONValue> ? never : StrictBody>;
62
+ formData: () => Promise<
63
+ StrictBody extends HttpFormData<infer HttpFormDataSchema>
64
+ ? StrictFormData<HttpFormDataSchema>
65
+ : StrictBody extends HttpSearchParams<infer HttpSearchParamsSchema>
66
+ ? StrictFormData<HttpSearchParamsSchema>
67
+ : FormData
68
+ >;
69
+ clone: () => this;
70
+ }
71
+
72
+ /**
73
+ * An HTTP response with a strictly-typed JSON body and status code. Fully compatible with the built-in
74
+ * {@link https://developer.mozilla.org/docs/Web/API/Response `Response`} class.
75
+ */
76
+ export interface HttpResponse<
77
+ StrictBody extends HttpBody.Loose = HttpBody,
78
+ StatusCode extends number = number,
79
+ StrictHeadersSchema extends HttpHeadersSchema = HttpHeadersSchema,
80
+ > extends Response {
81
+ ok: StatusCode extends HttpStatusCode.Information | HttpStatusCode.Success | HttpStatusCode.Redirection
82
+ ? true
83
+ : false;
84
+ status: StatusCode;
85
+ headers: StrictHeaders<StrictHeadersSchema>;
86
+ text: () => Promise<StrictBody extends string ? StrictBody : string>;
87
+ json: () => Promise<StrictBody extends string | Exclude<HttpBody, JSONValue> ? never : StrictBody>;
88
+ formData: () => Promise<
89
+ StrictBody extends HttpFormData<infer HttpFormDataSchema>
90
+ ? StrictFormData<HttpFormDataSchema>
91
+ : StrictBody extends HttpSearchParams<infer HttpSearchParamsSchema>
92
+ ? StrictFormData<HttpSearchParamsSchema>
93
+ : FormData
94
+ >;
95
+ clone: () => this;
96
+ }