Skip to content

Commit

Permalink
Merge pull request #23 from comake/version/2.1.0
Browse files Browse the repository at this point in the history
Version/2.1.0
  • Loading branch information
adlerfaulkner committed Jun 22, 2023
2 parents e71e962 + a12b936 commit 905ad51
Show file tree
Hide file tree
Showing 14 changed files with 707 additions and 102 deletions.
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

0 comments on commit 905ad51

Please sign in to comment.