Add optional stronger typing of requests and responses to ensure conformity of flattened function params
This commit is contained in:
parent
6d50f70258
commit
417040f2d5
|
@ -1,92 +0,0 @@
|
||||||
export const buildDatasourceEndpoints = API => ({
|
|
||||||
/**
|
|
||||||
* Gets a list of datasources.
|
|
||||||
*/
|
|
||||||
getDatasources: async () => {
|
|
||||||
return await API.get({
|
|
||||||
url: "/api/datasources",
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompts the server to build the schema for a datasource.
|
|
||||||
* @param datasourceId the datasource ID to build the schema for
|
|
||||||
* @param tablesFilter list of specific table names to be build the schema
|
|
||||||
*/
|
|
||||||
buildDatasourceSchema: async ({ datasourceId, tablesFilter }) => {
|
|
||||||
return await API.post({
|
|
||||||
url: `/api/datasources/${datasourceId}/schema`,
|
|
||||||
body: {
|
|
||||||
tablesFilter,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a datasource
|
|
||||||
* @param datasource the datasource to create
|
|
||||||
* @param fetchSchema whether to fetch the schema or not
|
|
||||||
* @param tablesFilter a list of tables to actually fetch rather than simply
|
|
||||||
* all that are accessible.
|
|
||||||
*/
|
|
||||||
createDatasource: async ({ datasource, fetchSchema, tablesFilter }) => {
|
|
||||||
return await API.post({
|
|
||||||
url: "/api/datasources",
|
|
||||||
body: {
|
|
||||||
datasource,
|
|
||||||
fetchSchema,
|
|
||||||
tablesFilter,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a datasource
|
|
||||||
* @param datasource the datasource to update
|
|
||||||
*/
|
|
||||||
updateDatasource: async datasource => {
|
|
||||||
return await API.put({
|
|
||||||
url: `/api/datasources/${datasource._id}`,
|
|
||||||
body: datasource,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a datasource.
|
|
||||||
* @param datasourceId the ID of the ddtasource to delete
|
|
||||||
* @param datasourceRev the rev of the datasource to delete
|
|
||||||
*/
|
|
||||||
deleteDatasource: async ({ datasourceId, datasourceRev }) => {
|
|
||||||
return await API.delete({
|
|
||||||
url: `/api/datasources/${datasourceId}/${datasourceRev}`,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a datasource configuration
|
|
||||||
* @param datasource the datasource configuration to validate
|
|
||||||
*/
|
|
||||||
validateDatasource: async datasource => {
|
|
||||||
return await API.post({
|
|
||||||
url: `/api/datasources/verify`,
|
|
||||||
body: { datasource },
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch table names available within the datasource, for filtering out undesired tables
|
|
||||||
* @param datasource the datasource configuration to use for fetching tables
|
|
||||||
*/
|
|
||||||
fetchInfoForDatasource: async datasource => {
|
|
||||||
return await API.post({
|
|
||||||
url: `/api/datasources/info`,
|
|
||||||
body: { datasource },
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchExternalSchema: async datasourceId => {
|
|
||||||
return await API.get({
|
|
||||||
url: `/api/datasources/${datasourceId}/schema/external`,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
import {
|
||||||
|
BuildSchemaFromSourceRequest,
|
||||||
|
BuildSchemaFromSourceResponse,
|
||||||
|
CreateDatasourceRequest,
|
||||||
|
CreateDatasourceResponse,
|
||||||
|
Datasource,
|
||||||
|
FetchDatasourceInfoRequest,
|
||||||
|
FetchDatasourceInfoResponse,
|
||||||
|
UpdateDatasourceRequest,
|
||||||
|
UpdateDatasourceResponse,
|
||||||
|
VerifyDatasourceRequest,
|
||||||
|
VerifyDatasourceResponse,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { BaseAPIClient } from "./types"
|
||||||
|
|
||||||
|
export interface DatasourceEndpoints {
|
||||||
|
getDatasources: () => Promise<Datasource[]>
|
||||||
|
buildDatasourceSchema: (
|
||||||
|
datasourceId: string,
|
||||||
|
tablesFilter?: BuildSchemaFromSourceRequest["tablesFilter"]
|
||||||
|
) => Promise<BuildSchemaFromSourceResponse>
|
||||||
|
createDatasource: (
|
||||||
|
request: CreateDatasourceRequest
|
||||||
|
) => Promise<CreateDatasourceResponse>
|
||||||
|
updateDatasource: (
|
||||||
|
datasource: Datasource
|
||||||
|
) => Promise<UpdateDatasourceResponse>
|
||||||
|
deleteDatasource: (id: string, rev: string) => Promise<void>
|
||||||
|
validateDatasource: (
|
||||||
|
datasource: Datasource
|
||||||
|
) => Promise<VerifyDatasourceResponse>
|
||||||
|
fetchInfoForDatasource: (
|
||||||
|
datasource: Datasource
|
||||||
|
) => Promise<FetchDatasourceInfoResponse>
|
||||||
|
fetchExternalSchema: (datasourceId: string) => Promise<{ schema: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildDatasourceEndpoints = (
|
||||||
|
API: BaseAPIClient
|
||||||
|
): DatasourceEndpoints => ({
|
||||||
|
/**
|
||||||
|
* Gets a list of datasources.
|
||||||
|
*/
|
||||||
|
getDatasources: async () => {
|
||||||
|
return await API.get({
|
||||||
|
url: "/api/datasources",
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompts the server to build the schema for a datasource.
|
||||||
|
*/
|
||||||
|
buildDatasourceSchema: async (datasourceId, tablesFilter?) => {
|
||||||
|
return await API.post<
|
||||||
|
BuildSchemaFromSourceRequest,
|
||||||
|
BuildSchemaFromSourceResponse
|
||||||
|
>({
|
||||||
|
url: `/api/datasources/${datasourceId}/schema`,
|
||||||
|
body: {
|
||||||
|
tablesFilter,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a datasource
|
||||||
|
*/
|
||||||
|
createDatasource: async request => {
|
||||||
|
return await API.post({
|
||||||
|
url: "/api/datasources",
|
||||||
|
body: request,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a datasource
|
||||||
|
*/
|
||||||
|
updateDatasource: async datasource => {
|
||||||
|
return await API.put<UpdateDatasourceRequest, UpdateDatasourceResponse>({
|
||||||
|
url: `/api/datasources/${datasource._id}`,
|
||||||
|
body: datasource,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a datasource.
|
||||||
|
*/
|
||||||
|
deleteDatasource: async (id: string, rev: string) => {
|
||||||
|
return await API.delete({
|
||||||
|
url: `/api/datasources/${id}/${rev}`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a datasource configuration
|
||||||
|
*/
|
||||||
|
validateDatasource: async (datasource: Datasource) => {
|
||||||
|
return await API.post<VerifyDatasourceRequest, VerifyDatasourceResponse>({
|
||||||
|
url: `/api/datasources/verify`,
|
||||||
|
body: { datasource },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch table names available within the datasource, for filtering out undesired tables
|
||||||
|
*/
|
||||||
|
fetchInfoForDatasource: async (datasource: Datasource) => {
|
||||||
|
return await API.post<
|
||||||
|
FetchDatasourceInfoRequest,
|
||||||
|
FetchDatasourceInfoResponse
|
||||||
|
>({
|
||||||
|
url: `/api/datasources/info`,
|
||||||
|
body: { datasource },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the external schema of a datasource
|
||||||
|
*/
|
||||||
|
fetchExternalSchema: async (datasourceId: string) => {
|
||||||
|
return await API.get({
|
||||||
|
url: `/api/datasources/${datasourceId}/schema/external`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
|
@ -103,7 +103,9 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performs an API call to the server.
|
// Performs an API call to the server.
|
||||||
const makeApiCall = async <T>(callConfig: APICallConfig): Promise<T> => {
|
const makeApiCall = async <RequestT, ResponseT>(
|
||||||
|
callConfig: APICallConfig<RequestT, ResponseT>
|
||||||
|
): Promise<ResponseT> => {
|
||||||
let { json, method, external, body, url, parseResponse, suppressErrors } =
|
let { json, method, external, body, url, parseResponse, suppressErrors } =
|
||||||
callConfig
|
callConfig
|
||||||
|
|
||||||
|
@ -124,7 +126,7 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build request body
|
// Build request body
|
||||||
let requestBody = body
|
let requestBody: RequestT | string = body
|
||||||
if (json) {
|
if (json) {
|
||||||
try {
|
try {
|
||||||
requestBody = JSON.stringify(body)
|
requestBody = JSON.stringify(body)
|
||||||
|
@ -139,7 +141,7 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
|
||||||
response = await fetch(url, {
|
response = await fetch(url, {
|
||||||
method,
|
method,
|
||||||
headers,
|
headers,
|
||||||
body: requestBody,
|
body: requestBody as any,
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -152,9 +154,9 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
|
||||||
handleMigrations(response)
|
handleMigrations(response)
|
||||||
try {
|
try {
|
||||||
if (parseResponse) {
|
if (parseResponse) {
|
||||||
return await parseResponse<T>(response)
|
return await parseResponse(response)
|
||||||
} else {
|
} else {
|
||||||
return (await response.json()) as T
|
return (await response.json()) as ResponseT
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
delete cache[url]
|
delete cache[url]
|
||||||
|
@ -180,28 +182,31 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
|
||||||
// Performs an API call to the server and caches the response.
|
// Performs an API call to the server and caches the response.
|
||||||
// Future invocation for this URL will return the cached result instead of
|
// Future invocation for this URL will return the cached result instead of
|
||||||
// hitting the server again.
|
// hitting the server again.
|
||||||
const makeCachedApiCall = async <T>(
|
const makeCachedApiCall = async <RequestT = void, ResponseT = void>(
|
||||||
callConfig: APICallConfig
|
callConfig: APICallConfig<RequestT, ResponseT>
|
||||||
): Promise<T> => {
|
): Promise<ResponseT> => {
|
||||||
const identifier = callConfig.url
|
const identifier = callConfig.url
|
||||||
if (!cache[identifier]) {
|
if (!cache[identifier]) {
|
||||||
cache[identifier] = makeApiCall(callConfig)
|
cache[identifier] = makeApiCall(callConfig)
|
||||||
cache[identifier] = await cache[identifier]
|
cache[identifier] = await cache[identifier]
|
||||||
}
|
}
|
||||||
return (await cache[identifier]) as T
|
return (await cache[identifier]) as ResponseT
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructs an API call function for a particular HTTP method
|
// Constructs an API call function for a particular HTTP method
|
||||||
const requestApiCall =
|
const requestApiCall =
|
||||||
(method: HTTPMethod) =>
|
(method: HTTPMethod) =>
|
||||||
async <T>(params: APICallParams): Promise<T> => {
|
async <RequestT = void, ResponseT = void>(
|
||||||
|
params: APICallParams<RequestT, ResponseT>
|
||||||
|
): Promise<ResponseT> => {
|
||||||
try {
|
try {
|
||||||
let callConfig: APICallConfig = {
|
let callConfig: APICallConfig<RequestT, ResponseT> = {
|
||||||
json: true,
|
json: true,
|
||||||
external: false,
|
external: false,
|
||||||
suppressErrors: false,
|
suppressErrors: false,
|
||||||
cache: false,
|
cache: false,
|
||||||
method,
|
method,
|
||||||
|
body: params.body,
|
||||||
...params,
|
...params,
|
||||||
}
|
}
|
||||||
let { url, cache, external } = callConfig
|
let { url, cache, external } = callConfig
|
||||||
|
@ -212,7 +217,7 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
|
||||||
// Cache the request if possible and desired
|
// Cache the request if possible and desired
|
||||||
const cacheRequest = cache && config?.enableCaching
|
const cacheRequest = cache && config?.enableCaching
|
||||||
const handler = cacheRequest ? makeCachedApiCall : makeApiCall
|
const handler = cacheRequest ? makeCachedApiCall : makeApiCall
|
||||||
return await handler<T>(callConfig)
|
return await handler(callConfig)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (config?.onError) {
|
if (config?.onError) {
|
||||||
config.onError(error)
|
config.onError(error)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { AuthEndpoints } from "./auth"
|
||||||
import { AutomationEndpoints } from "./automations"
|
import { AutomationEndpoints } from "./automations"
|
||||||
import { BackupEndpoints } from "./backups"
|
import { BackupEndpoints } from "./backups"
|
||||||
import { ConfigEndpoints } from "./configs"
|
import { ConfigEndpoints } from "./configs"
|
||||||
|
import { DatasourceEndpoints } from "./datasources"
|
||||||
|
|
||||||
export enum HTTPMethod {
|
export enum HTTPMethod {
|
||||||
POST = "POST",
|
POST = "POST",
|
||||||
|
@ -25,25 +26,42 @@ export type APIClientConfig = {
|
||||||
onMigrationDetected?: (migration: string) => void
|
onMigrationDetected?: (migration: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type APICallConfig = {
|
export type APICallConfig<RequestT = null, ResponseT = void> = {
|
||||||
method: HTTPMethod
|
method: HTTPMethod
|
||||||
url: string
|
url: string
|
||||||
|
body: RequestT
|
||||||
json: boolean
|
json: boolean
|
||||||
external: boolean
|
external: boolean
|
||||||
suppressErrors: boolean
|
suppressErrors: boolean
|
||||||
cache: boolean
|
cache: boolean
|
||||||
body?: any
|
parseResponse?: (response: Response) => Promise<ResponseT> | ResponseT
|
||||||
parseResponse?: <T>(response: Response) => Promise<T> | T
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type APICallParams = Pick<APICallConfig, "url"> & Partial<APICallConfig>
|
export type APICallParams<
|
||||||
|
RequestT = null,
|
||||||
|
ResponseT = void
|
||||||
|
> = RequestT extends null
|
||||||
|
? Pick<APICallConfig<RequestT, ResponseT>, "url"> &
|
||||||
|
Partial<APICallConfig<RequestT, ResponseT>>
|
||||||
|
: Pick<APICallConfig<RequestT, ResponseT>, "url" | "body"> &
|
||||||
|
Partial<APICallConfig<RequestT, ResponseT>>
|
||||||
|
|
||||||
export type BaseAPIClient = {
|
export type BaseAPIClient = {
|
||||||
post: <T>(params: APICallParams) => Promise<T>
|
post: <RequestT = null, ResponseT = void>(
|
||||||
get: <T>(params: APICallParams) => Promise<T>
|
params: APICallParams<RequestT, ResponseT>
|
||||||
put: <T>(params: APICallParams) => Promise<T>
|
) => Promise<ResponseT>
|
||||||
delete: <T>(params: APICallParams) => Promise<T>
|
get: <ResponseT = void>(
|
||||||
patch: <T>(params: APICallParams) => Promise<T>
|
params: APICallParams<undefined | null, ResponseT>
|
||||||
|
) => Promise<ResponseT>
|
||||||
|
put: <RequestT = null, ResponseT = void>(
|
||||||
|
params: APICallParams<RequestT, ResponseT>
|
||||||
|
) => Promise<ResponseT>
|
||||||
|
delete: <RequestT = null, ResponseT = void>(
|
||||||
|
params: APICallParams<RequestT, ResponseT>
|
||||||
|
) => Promise<ResponseT>
|
||||||
|
patch: <RequestT = null, ResponseT = void>(
|
||||||
|
params: APICallParams<RequestT, ResponseT>
|
||||||
|
) => Promise<ResponseT>
|
||||||
error: (message: string) => void
|
error: (message: string) => void
|
||||||
invalidateCache: () => void
|
invalidateCache: () => void
|
||||||
getAppID: () => string
|
getAppID: () => string
|
||||||
|
@ -58,4 +76,5 @@ export type APIClient = BaseAPIClient &
|
||||||
AuthEndpoints &
|
AuthEndpoints &
|
||||||
AutomationEndpoints &
|
AutomationEndpoints &
|
||||||
BackupEndpoints &
|
BackupEndpoints &
|
||||||
ConfigEndpoints & { [key: string]: any }
|
ConfigEndpoints &
|
||||||
|
DatasourceEndpoints & { [key: string]: any }
|
||||||
|
|
Loading…
Reference in New Issue