Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version/2.1.0 #23

Merged
merged 5 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Standard SDK is an open source software package that provides you with a single

Developers commonly spent lots of time installing SDKs, reading documentation, and figuring out how to use each SDK to build integrations using APIs and to add features to their application. Instead of going through this time consuming process installing and learning many different SDKs, a developer can just install Standard SDK to build any integration their application requires. In addition to saving time, removing dependencies on all those SDKs in favor of just one can reduce your application's build size, and make it easier to onboard developers to your codebase.

[![Standard SDK Introductory Demo](https://img.youtube.com/vi/hXgUag6VX30/0.jpg)](https://www.youtube.com/watch?v=hXgUag6VX30)

### How it works

Standard SDK is powered by open source standardized API specifications like [OpenAPI](https://www.openapis.org/). It uses these API specifications to know how to send properly formatted web requests, RPC messages, SQL queries, etc. to the APIs you want your application to integrate with. The API specifications which Standard SDK works with or is planning to add support for include:
Expand Down
193 changes: 128 additions & 65 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@comake/standard-sdk-js",
"version": "2.0.0",
"version": "2.1.0",
"description": "An open source SDK to integrate and interact with any API",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down Expand Up @@ -56,7 +56,7 @@
"dist"
],
"dependencies": {
"@comake/openapi-operation-executor": "^0.7.1",
"@comake/openapi-operation-executor": "^0.10.0",
"@comake/skl-js-engine": "^0.1.0",
"@commitlint/cli": "^17.4.1",
"@commitlint/config-conventional": "^17.4.0",
Expand All @@ -82,6 +82,6 @@
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"ts-toolbelt": "^9.6.0",
"typescript": "^4.9.4"
"typescript": "^5.1.0"
}
}
10 changes: 6 additions & 4 deletions src/ApiOperationNamespace.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { ApiSpecType } from './ApiSpecOptions';
import type { ApiSpecOptions } from './ApiSpecOptions';
import type { OpenApiOperationNamespace } from './openapi-types/OpenApiOperationNamespace';

export type ApiOperationNamespace<T extends ApiSpecType, TSpec> = {
openapi: OpenApiOperationNamespace<TSpec>;
}[T];
export type ApiOperationNamespace<
T extends ApiSpecOptions
> = T['type'] extends 'openapi'
? OpenApiOperationNamespace<T['value'], T['defaultConfiguration']>
: never;
6 changes: 1 addition & 5 deletions src/OperationHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
export type OperationHandler = (
args?: any,
configuration?: any,
options?: any
) => Promise<any>;
export type OperationHandler = (args: any, configuration: any, options?: any) => Promise<any>;
26 changes: 14 additions & 12 deletions src/StandardSdk.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { OpenApiClientConfiguration } from '@comake/openapi-operation-executor';
import type { SKLEngineOptions } from '@comake/skl-js-engine';
import { SKLEngine } from '@comake/skl-js-engine';
import type { ApiOperationNamespace } from './ApiOperationNamespace';
import type { ApiSpecOptions, ApiSpecs, ApiSpecType } from './ApiSpecOptions';
import type { ApiSpecOptions, ApiSpecs } from './ApiSpecOptions';
import { OpenApiOperationExecutor } from './operation-executor/OpenApiOperationExecutor';
import type { OperationExecutor } from './operation-executor/OperationExecutor';
import type { OperationHandler } from './OperationHandler';
Expand Down Expand Up @@ -39,23 +40,23 @@ class StandardSDKBase<T extends ApiSpecs> {

private createApiOperationNamespaces<TS extends ApiSpecs>(
apiSpecs: TS,
): Record<keyof TS, ApiOperationNamespace<ApiSpecType, any>> {
): Record<keyof TS, ApiOperationNamespace<ApiSpecOptions>> {
return Object.entries(apiSpecs)
.reduce(<TR extends ApiSpecOptions>(
obj: Record<keyof TS, ApiOperationNamespace<ApiSpecType, any>>,
obj: Record<keyof TS, ApiOperationNamespace<ApiSpecOptions>>,
[ apiSpecName, specObject ]: [string, TR],
): Record<keyof TS, ApiOperationNamespace<ApiSpecType, any>> => {
): Record<keyof TS, ApiOperationNamespace<ApiSpecOptions>> => {
const executor = this.generateExecutorForApiSpecOptions(specObject);
const operationHandler = this.buildOperationHandlerForApiSpec(executor);
const operationHandler = this.buildOperationHandlerForApiSpec(executor, specObject.defaultConfiguration);
return {
...obj,
[apiSpecName]: new Proxy(
{} as ApiOperationNamespace<ApiSpecType, any>,
{} as ApiOperationNamespace<ApiSpecOptions>,
{ get: operationHandler },
),
};
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter
}, {} as Record<keyof TS, ApiOperationNamespace<any, any>>);
}, {} as Record<keyof TS, ApiOperationNamespace<ApiSpecOptions>>);
}

private generateExecutorForApiSpecOptions<TR extends ApiSpecOptions>(
Expand All @@ -67,14 +68,15 @@ class StandardSDKBase<T extends ApiSpecs> {
throw new Error(`API Specification type ${apiSpec.type} is not supported.`);
}

private buildOperationHandlerForApiSpec<TR extends ApiSpecType>(
private buildOperationHandlerForApiSpec(
executor: OperationExecutor,
defaultConfiguration?: OpenApiClientConfiguration,
): (
target: ApiOperationNamespace<TR, any>,
target: ApiOperationNamespace<ApiSpecOptions>,
operation: string,
) => OperationHandler {
return (
target: ApiOperationNamespace<TR, any>,
target: ApiOperationNamespace<ApiSpecOptions>,
operation: string,
): OperationHandler =>
async(
Expand All @@ -86,13 +88,13 @@ class StandardSDKBase<T extends ApiSpecs> {
operation,
args,
configuration,
options,
{ ...defaultConfiguration, ...options },
);
}
}

type NamespacedApiOperationNamespace<T extends ApiSpecs> = {
[key in keyof T]: ApiOperationNamespace<T[key]['type'], T[key]['value']>
[key in keyof T]: ApiOperationNamespace<T[key]>
};

export type StandardSDK<T extends ApiSpecs> = StandardSDKBase<T> & NamespacedApiOperationNamespace<T>;
Expand Down
2 changes: 1 addition & 1 deletion src/openapi-types/OpenApiArgTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,4 @@ export type OpenApiArgTypes<
TSpec extends OpenApi,
TOperation extends string = string,
TParams = ArgsOfOpenApiOperation<TSpec, TOperation>
> = [TParams] extends [never] ? any : TParams;
> = [TParams] extends [never] ? undefined : TParams;
102 changes: 102 additions & 0 deletions src/openapi-types/OpenApiConfigurationTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type {
OpenApi,
Operation,
PathItem,
SecurityRequirement,
OpenApiClientConfiguration,
} from '@comake/openapi-operation-executor';
import type { Merged } from '../type-utils/Merged';
import type { UnionToIntersection } from '../type-utils/UnionToIntersection';
import type { OpenApiOperationType } from './OpenApiOperationType';

type DistributiveOptional<T, TK extends string> = T extends any
? OpenApiClientConfiguration & Omit<T, TK>
: never;

type AnyFunction = (...args: any[]) => any;

type ExpandRecursively<T> = T extends object
? T extends Promise<any>
? T
: T extends AnyFunction
? T
: T extends infer O ? {[K in keyof O]: ExpandRecursively<O[K]> } : never
: T;

type UnionOfRequired<T, TH = Merged<UnionToIntersection<T>>> =
T extends any
? keyof T extends 'bearerToken' | 'username' | 'password' | 'accessToken'
? never
: TH & Required<T>
: never;

type OpenApiSecurityRequirementToType<
T extends SecurityRequirement,
TSecurityTypes extends Record<string, OpenApiClientConfiguration>,
TInter = {[K in keyof T & string]: TSecurityTypes[K] }[keyof T & string],
> = keyof TInter extends 'apiKey'
? UnionOfRequired<TInter>
: TInter;

type OpenApiSecurityRequirementToTypeWithDefaults<
T extends SecurityRequirement,
TSecurityTypes extends Record<string, OpenApiClientConfiguration>,
TDefaultConfig extends string | undefined,
TConfig = OpenApiSecurityRequirementToType<T, TSecurityTypes>,
> = TDefaultConfig extends string
? DistributiveOptional<TConfig, TDefaultConfig>
: OpenApiClientConfiguration & TConfig;

type OpenApiSecurityRequirementToTypes<
T extends readonly SecurityRequirement[],
TSecurityTypes extends Record<string, OpenApiClientConfiguration>,
TDefaultConfig extends string | undefined,
> = {[K in keyof T]: OpenApiSecurityRequirementToTypeWithDefaults<T[K], TSecurityTypes, TDefaultConfig> }[number];

type ConfigurationOfOperationInPathItemIfDefined<
T extends PathItem,
TOperation extends string,
TSpec extends OpenApi,
TDefaultConfig extends string | undefined,
TSecurityTypes extends Record<string, OpenApiClientConfiguration>,
TOperationObject = Extract<T[keyof T & OpenApiOperationType], { operationId: TOperation }>
> = [TOperationObject] extends [never]
? never
: TOperationObject extends Operation
? TOperationObject['security'] extends readonly SecurityRequirement[]
? OpenApiSecurityRequirementToTypes<TOperationObject['security'], TSecurityTypes, TDefaultConfig>
: TSpec['security'] extends readonly SecurityRequirement[]
? OpenApiSecurityRequirementToTypes<TSpec['security'], TSecurityTypes, TDefaultConfig>
: never
: never;

type ConfigurationOfOpenApiOperation<
T extends OpenApi,
TOperation extends string,
TDefaultConfig extends string | undefined,
TSecurityTypes extends Record<string, OpenApiClientConfiguration>
> = {
[key in keyof T['paths']]: ConfigurationOfOperationInPathItemIfDefined<
T['paths'][key],
TOperation,
T,
TDefaultConfig,
TSecurityTypes
>
}[keyof T['paths']];

type UndefinedOrStringKeyOf<T extends Record<string, any> | undefined | unknown> =
T extends object
? keyof T & string
: undefined;

export type OpenApiClientConfigurationTypes<
TSpec extends OpenApi,
TOperation extends string,
TSecurityTypes extends Record<string, OpenApiClientConfiguration>,
TDefaultConfig extends OpenApiClientConfiguration | undefined = undefined,
TDefaultConfigKeys extends string | undefined = UndefinedOrStringKeyOf<TDefaultConfig>,
TConfig = ExpandRecursively<
ConfigurationOfOpenApiOperation<TSpec, TOperation, TDefaultConfigKeys, TSecurityTypes>
>
> = [TConfig] extends [never] ? undefined : TConfig;
87 changes: 78 additions & 9 deletions src/openapi-types/OpenApiOperationNamespace.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,47 @@
import type { OpenApi, OpenApiClientConfiguration, Operation, PathItem } from '@comake/openapi-operation-executor';
import type {
OpenApi,
OpenApiClientConfiguration,
Operation,
PathItem,
Components,
SecurityScheme,
APIKeySecurityScheme,
HTTPSecurityScheme,
OAuth2SecurityScheme,
} from '@comake/openapi-operation-executor';
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import type { OpenApiArgTypes } from './OpenApiArgTypes';
import type { OpenApiClientConfigurationTypes } from './OpenApiConfigurationTypes';
import type { OpenApiOperationType } from './OpenApiOperationType';

type OpenApiSecuritySchemeToType<T extends SecurityScheme> =
T extends APIKeySecurityScheme
? {[K in T['name']]?: OpenApiClientConfiguration['apiKey'] } |
{ apiKey?: OpenApiClientConfiguration['apiKey'] }
: T extends OAuth2SecurityScheme
? { accessToken: OpenApiClientConfiguration['accessToken'] }
: T extends HTTPSecurityScheme
? T['scheme'] extends 'basic'
? {
username: OpenApiClientConfiguration['username'];
password: OpenApiClientConfiguration['password'];
}
: T['scheme'] extends 'bearer'
? { bearerToken: OpenApiClientConfiguration['bearerToken'] }
: never
: never;

export type OpenApiSecuritySchemesToTypes<
TSpec extends OpenApi
> = TSpec['components'] extends Components
? TSpec['components']['securitySchemes'] extends Record<string, SecurityScheme>
? {
[K in keyof TSpec['components']['securitySchemes']]:
OpenApiSecuritySchemeToType<TSpec['components']['securitySchemes'][K]>;
}
: Record<string, never>
: Record<string, never>;

type OperationsOfPathItem<T extends PathItem> = {
[operationType in keyof T & OpenApiOperationType]: T[operationType] extends Operation
? T[operationType]['operationId']
Expand All @@ -13,16 +52,46 @@ type OperationIdsOfOpenApi<T extends OpenApi> = {
[path in keyof T['paths']]: Exclude<OperationsOfPathItem<T['paths'][path]>, undefined>
}[keyof T['paths']];

type OpenApiOperationInterface<T extends OpenApi> = {
[operation in OperationIdsOfOpenApi<T>]: (
args?: OpenApiArgTypes<T, operation>,
configuration?: OpenApiClientConfiguration,
options?: AxiosRequestConfig
) => Promise<AxiosResponse>
type OpenApiOperationInterfaceForOperation<
T extends string,
TSpec extends OpenApi,
TDefaultConfig extends OpenApiClientConfiguration | undefined,
TSecurityTypes extends Record<string, OpenApiClientConfiguration> = OpenApiSecuritySchemesToTypes<TSpec>,
TArgs = OpenApiArgTypes<TSpec, T>,
TConfig = OpenApiClientConfigurationTypes<TSpec, T, TSecurityTypes, TDefaultConfig>
> = TArgs extends undefined
? [TConfig] extends undefined
// eslint-disable-next-line max-len
? (args?: Record<string, any>, configuration?: OpenApiClientConfiguration, options?: AxiosRequestConfig) => Promise<AxiosResponse>
: [TConfig] extends [undefined]
// eslint-disable-next-line max-len
? (args: Record<string, any>, configuration?: OpenApiClientConfiguration, options?: AxiosRequestConfig) => Promise<AxiosResponse>
: (args: Record<string, any>, configuration: TConfig, options?: AxiosRequestConfig) => Promise<AxiosResponse>
: [TConfig] extends undefined
? (args: TArgs, configuration?: OpenApiClientConfiguration, options?: AxiosRequestConfig) => Promise<AxiosResponse>
: [TConfig] extends [undefined]
// eslint-disable-next-line max-len
? (args: TArgs, configuration?: OpenApiClientConfiguration, options?: AxiosRequestConfig) => Promise<AxiosResponse>
: (args: TArgs, configuration: TConfig, options?: AxiosRequestConfig) => Promise<AxiosResponse>;

type OpenApiOperationInterface<
T extends OpenApi,
TDefaultConfig extends OpenApiClientConfiguration | undefined,
TSecurityTypes extends Record<string, OpenApiClientConfiguration> = OpenApiSecuritySchemesToTypes<T>,
> = {
[operation in OperationIdsOfOpenApi<T>]: OpenApiOperationInterfaceForOperation<
operation,
T,
TDefaultConfig,
TSecurityTypes
>
};

export type OpenApiOperationNamespace<T> = T extends OpenApi
? OpenApiOperationInterface<T>
export type OpenApiOperationNamespace<
T extends string | OpenApi,
TDefaultConfig extends OpenApiClientConfiguration | undefined,
> = T extends OpenApi
? OpenApiOperationInterface<T, TDefaultConfig>
: Record<string, (
args?: any,
configuration?: OpenApiClientConfiguration,
Expand Down
6 changes: 5 additions & 1 deletion src/openapi-types/OpenApiSpecOptions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { OpenApi } from '@comake/openapi-operation-executor';
import type { OpenApi, OpenApiClientConfiguration } from '@comake/openapi-operation-executor';
import type { BaseApiSpecOptions } from '../BaseApiSpecOptions';

export interface OpenApiSpecOptions extends BaseApiSpecOptions {
Expand All @@ -11,4 +11,8 @@ export interface OpenApiSpecOptions extends BaseApiSpecOptions {
* Either an OpenApi conformant JSON object, or a stringified representation of one.
*/
readonly value: string | OpenApi;
/**
* Default configuration supplied to every operation of this API.
*/
readonly defaultConfiguration?: OpenApiClientConfiguration;
}
2 changes: 1 addition & 1 deletion test/integration/OperationExecution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describeIf('docker', 'Operation execution', (): void => {
});
const response = await ssdk.ticketmaster.SearchEvents(
{ dmaId: '220' },
{ apiKey: process.env.TICKETMASTER_APIKEY },
{ apiKey: process.env.TICKETMASTER_APIKEY! },
);
expect(response.data).toBeInstanceOf(Object);
expect(response.data._embedded.events).toBeInstanceOf(Array);
Expand Down
Loading