@trayio/cdk-dsl
Advanced tools
Comparing version
@@ -1,34 +0,55 @@ | ||
import { HttpMethod, HttpRequest, HttpHeaderValue, HttpResponse } from '@trayio/commons/http/Http'; | ||
import { OperationHandlerAuth, OperationHandlerContext, OperationHandlerResult } from './OperationHandler'; | ||
import { HttpContentType, HttpMethod, HttpRequest, HttpHeaderValue, HttpResponse } from '@trayio/commons/http/Http'; | ||
import { DynamicType } from '@trayio/commons/dynamictype/DynamicType'; | ||
import * as O from 'fp-ts/Option'; | ||
import { OperationHandlerAuth, OperationHandlerContext, OperationHandlerResult, FileReference } from './OperationHandler'; | ||
export type HttpOperationBody = FileReference | string | DynamicType | undefined; | ||
export declare class HttpOperationRequestBuilder { | ||
private readonly path; | ||
private readonly method; | ||
private readonly request; | ||
constructor(method: HttpMethod, path: string, request: HttpRequest); | ||
withBodyAsJson<T extends DynamicType>(body: T): HttpOperationRequest; | ||
withBodyAsText(body: string): HttpOperationRequest; | ||
withoutBody(): HttpOperationRequest; | ||
addPathParameter(name: string, value: string): HttpOperationRequestBuilder; | ||
addQueryString(name: string, value: string): HttpOperationRequestBuilder; | ||
withBearerToken(token: string): HttpOperationRequestBuilder; | ||
addHeader(name: string, value: HttpHeaderValue): HttpOperationRequestBuilder; | ||
} | ||
export declare class HttpOperationRequest { | ||
private jsonSerialization; | ||
readonly path: string; | ||
readonly method: HttpMethod; | ||
readonly request: HttpRequest; | ||
constructor(method: HttpMethod, path: string, request: HttpRequest); | ||
withBodyAsJson<T>(body: T): HttpOperationRequest; | ||
withBodyAsText(body: string): HttpOperationRequest; | ||
addPathParameter(name: string, value: string): HttpOperationRequest; | ||
addQueryString(name: string, value: string): HttpOperationRequest; | ||
withBearerToken(token: string): HttpOperationRequest; | ||
addHeader(name: string, value: HttpHeaderValue): HttpOperationRequest; | ||
readonly contentType: O.Option<HttpContentType>; | ||
readonly body: HttpOperationBody; | ||
constructor(path: string, method: HttpMethod, request: HttpRequest, contentType: O.Option<HttpContentType>, body: HttpOperationBody); | ||
} | ||
export type HttpOperationRequestHandler<AUTH extends OperationHandlerAuth<unknown, unknown>, IN> = (ctx: OperationHandlerContext<AUTH>, input: IN, request: HttpOperationRequest) => HttpOperationRequest; | ||
export declare class HttpOperationResponse { | ||
private jsonSerialization; | ||
export type HttpOperationRequestHandler<AUTH extends OperationHandlerAuth<unknown, unknown>, IN> = (ctx: OperationHandlerContext<AUTH>, input: IN, request: HttpOperationRequestBuilder) => HttpOperationRequest; | ||
export type HttpOperationResponseErrorHandling<OUT> = () => OperationHandlerResult<OUT>; | ||
export type HttpOperationResponseParser<BODY, OUT> = (body: BODY) => OperationHandlerResult<OUT>; | ||
export declare class HttpOperationResponseBuilder<OUT> { | ||
private response; | ||
constructor(response: HttpResponse); | ||
private errorHandling; | ||
constructor(response: HttpResponse, errorHandling: HttpOperationResponseErrorHandling<OUT>); | ||
getStatusCode(): number; | ||
getHeader(name: string): HttpHeaderValue | undefined; | ||
parseWithEmptyBody(): OperationHandlerResult<undefined>; | ||
parseWithBodyAsText(): OperationHandlerResult<string>; | ||
parseWithBodyAsJson<T>(): OperationHandlerResult<T>; | ||
withErrorHandling(errorHandling: HttpOperationResponseErrorHandling<OUT>): HttpOperationResponseBuilder<OUT>; | ||
parseWithoutBody(responseParser: HttpOperationResponseParser<undefined, OUT>): HttpOperationResponse<OUT>; | ||
parseWithBodyAsText(responseParser: HttpOperationResponseParser<string, OUT>): HttpOperationResponse<OUT>; | ||
parseWithBodyAsJson<T extends DynamicType>(responseParser?: HttpOperationResponseParser<T, OUT>): HttpOperationResponse<OUT>; | ||
} | ||
export type HttpOperationResponseHandler<OUT> = (response: HttpOperationResponse) => OperationHandlerResult<OUT>; | ||
export declare class HttpOperationResponse<OUT> { | ||
readonly response: HttpResponse; | ||
readonly errorHandling: HttpOperationResponseErrorHandling<OUT>; | ||
readonly contentType: O.Option<HttpContentType>; | ||
readonly responseParser: HttpOperationResponseParser<any, OUT>; | ||
constructor(response: HttpResponse, errorHandling: HttpOperationResponseErrorHandling<OUT>, contentType: O.Option<HttpContentType>, responseParser: HttpOperationResponseParser<any, OUT>); | ||
} | ||
export type HttpOperationResponseHandler<AUTH extends OperationHandlerAuth<unknown, unknown>, IN, OUT> = (ctx: OperationHandlerContext<AUTH>, input: IN, response: HttpOperationResponseBuilder<OUT>) => HttpOperationResponse<OUT>; | ||
export declare class HttpOperationHandler<AUTH extends OperationHandlerAuth<unknown, unknown>, IN, OUT> { | ||
readonly _tag: 'HttpOperationHandler'; | ||
readonly request: HttpOperationRequest; | ||
readonly request: HttpOperationRequestBuilder; | ||
readonly requestHandler: HttpOperationRequestHandler<AUTH, IN>; | ||
readonly responseHandler: HttpOperationResponseHandler<OUT>; | ||
constructor(request: HttpOperationRequest, requestHandler: HttpOperationRequestHandler<AUTH, IN>, responseHandler: HttpOperationResponseHandler<OUT>); | ||
readonly responseHandler: HttpOperationResponseHandler<AUTH, IN, OUT>; | ||
constructor(request: HttpOperationRequestBuilder, requestHandler: HttpOperationRequestHandler<AUTH, IN>, responseHandler: HttpOperationResponseHandler<AUTH, IN, OUT>); | ||
} | ||
@@ -38,8 +59,8 @@ export declare class HttpOperationHandlerResponseConfiguration<AUTH extends OperationHandlerAuth<unknown, unknown>, IN, OUT> { | ||
private requestHandler; | ||
constructor(request: HttpOperationRequest, requestHandler: HttpOperationRequestHandler<AUTH, IN>); | ||
handleResponse(responseHandler: HttpOperationResponseHandler<OUT>): HttpOperationHandler<AUTH, IN, OUT>; | ||
constructor(request: HttpOperationRequestBuilder, requestHandler: HttpOperationRequestHandler<AUTH, IN>); | ||
handleResponse(responseHandler: HttpOperationResponseHandler<AUTH, IN, OUT>): HttpOperationHandler<AUTH, IN, OUT>; | ||
} | ||
export declare class HttpOperationHandlerRequestConfiguration<AUTH extends OperationHandlerAuth<unknown, unknown>, IN, OUT> { | ||
private request; | ||
constructor(request: HttpOperationRequest); | ||
constructor(request: HttpOperationRequestBuilder); | ||
handleRequest(requestHandler: HttpOperationRequestHandler<AUTH, IN>): HttpOperationHandlerResponseConfiguration<AUTH, IN, OUT>; | ||
@@ -46,0 +67,0 @@ } |
@@ -26,9 +26,8 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.HttpOperationHandlerConfiguration = exports.HttpOperationHandlerRequestConfiguration = exports.HttpOperationHandlerResponseConfiguration = exports.HttpOperationHandler = exports.HttpOperationResponse = exports.HttpOperationRequest = void 0; | ||
exports.HttpOperationHandlerConfiguration = exports.HttpOperationHandlerRequestConfiguration = exports.HttpOperationHandlerResponseConfiguration = exports.HttpOperationHandler = exports.HttpOperationResponse = exports.HttpOperationResponseBuilder = exports.HttpOperationRequest = exports.HttpOperationRequestBuilder = void 0; | ||
const Http_1 = require("@trayio/commons/http/Http"); | ||
const JsonSerialization_1 = require("@trayio/commons/serialization/JsonSerialization"); | ||
const BufferExtensions_1 = require("@trayio/commons/buffer/BufferExtensions"); | ||
const fs = __importStar(require("fs")); | ||
const pathLib = __importStar(require("path")); | ||
const E = __importStar(require("fp-ts/Either")); | ||
const O = __importStar(require("fp-ts/Option")); | ||
const OperationHandler_1 = require("./OperationHandler"); | ||
@@ -44,5 +43,4 @@ // TODO: move to TTM | ||
const packageVersion = readPackageVersion(); | ||
class HttpOperationRequest { | ||
class HttpOperationRequestBuilder { | ||
constructor(method, path, request) { | ||
this.jsonSerialization = new JsonSerialization_1.JsonSerialization(); | ||
this.method = method; | ||
@@ -53,13 +51,19 @@ this.path = path; | ||
withBodyAsJson(body) { | ||
const serializedBody = this.jsonSerialization.serialize(body); | ||
return new HttpOperationRequest(this.method, this.path, Object.assign(Object.assign({}, this.request), { body: BufferExtensions_1.BufferExtensions.arrayBufferToReadable(serializedBody) })).addHeader(Http_1.HttpHeader.ContentType, Http_1.HttpContentType.Json); | ||
const contentType = Http_1.HttpContentType.Json; | ||
const requestWithHeader = this.addHeader(Http_1.HttpHeader.ContentType, contentType); | ||
return new HttpOperationRequest(requestWithHeader.path, requestWithHeader.method, requestWithHeader.request, O.some(contentType), body); | ||
} | ||
withBodyAsText(body) { | ||
return new HttpOperationRequest(this.method, this.path, Object.assign(Object.assign({}, this.request), { body: BufferExtensions_1.BufferExtensions.arrayBufferToReadable(new TextEncoder().encode(body)) })).addHeader(Http_1.HttpHeader.ContentType, Http_1.HttpContentType.Text); | ||
const contentType = Http_1.HttpContentType.Text; | ||
const requestWithHeader = this.addHeader(Http_1.HttpHeader.ContentType, contentType); | ||
return new HttpOperationRequest(requestWithHeader.path, requestWithHeader.method, requestWithHeader.request, O.some(contentType), body); | ||
} | ||
withoutBody() { | ||
return new HttpOperationRequest(this.path, this.method, this.request, O.none, undefined); | ||
} | ||
addPathParameter(name, value) { | ||
return new HttpOperationRequest(this.method, this.path, Object.assign(Object.assign({}, this.request), { pathParams: Object.assign(Object.assign({}, this.request.pathParams), { [name]: value }) })); | ||
return new HttpOperationRequestBuilder(this.method, this.path, Object.assign(Object.assign({}, this.request), { pathParams: Object.assign(Object.assign({}, this.request.pathParams), { [name]: value }) })); | ||
} | ||
addQueryString(name, value) { | ||
return new HttpOperationRequest(this.method, this.path, Object.assign(Object.assign({}, this.request), { queryString: Object.assign(Object.assign({}, this.request.queryString), { [name]: value }) })); | ||
return new HttpOperationRequestBuilder(this.method, this.path, Object.assign(Object.assign({}, this.request), { queryString: Object.assign(Object.assign({}, this.request.queryString), { [name]: value }) })); | ||
} | ||
@@ -70,10 +74,20 @@ withBearerToken(token) { | ||
addHeader(name, value) { | ||
return new HttpOperationRequest(this.method, this.path, Object.assign(Object.assign({}, this.request), { headers: Object.assign(Object.assign({}, this.request.headers), { [name]: value }) })); | ||
return new HttpOperationRequestBuilder(this.method, this.path, Object.assign(Object.assign({}, this.request), { headers: Object.assign(Object.assign({}, this.request.headers), { [name]: value }) })); | ||
} | ||
} | ||
exports.HttpOperationRequestBuilder = HttpOperationRequestBuilder; | ||
class HttpOperationRequest { | ||
constructor(path, method, request, contentType, body) { | ||
this.path = path; | ||
this.method = method; | ||
this.request = request; | ||
this.contentType = contentType; | ||
this.body = body; | ||
} | ||
} | ||
exports.HttpOperationRequest = HttpOperationRequest; | ||
class HttpOperationResponse { | ||
constructor(response) { | ||
this.jsonSerialization = new JsonSerialization_1.JsonSerialization(); | ||
class HttpOperationResponseBuilder { | ||
constructor(response, errorHandling) { | ||
this.response = response; | ||
this.errorHandling = errorHandling; | ||
} | ||
@@ -86,32 +100,24 @@ getStatusCode() { | ||
} | ||
parseWithEmptyBody() { | ||
if (this.response.statusCode >= 300) { | ||
return OperationHandler_1.OperationHandlerResult.failure(OperationHandler_1.OperationHandlerError.apiError(`API returned a status code of ${this.response.statusCode}`, { | ||
statusCode: this.response.statusCode, | ||
})); | ||
} | ||
return OperationHandler_1.OperationHandlerResult.success(undefined); | ||
withErrorHandling(errorHandling) { | ||
return new HttpOperationResponseBuilder(this.response, errorHandling); | ||
} | ||
parseWithBodyAsText() { | ||
if (this.response.statusCode >= 300) { | ||
return OperationHandler_1.OperationHandlerResult.failure(OperationHandler_1.OperationHandlerError.apiError(`API returned a status code of ${this.response.statusCode}`, { | ||
statusCode: this.response.statusCode, | ||
})); | ||
} | ||
return OperationHandler_1.OperationHandlerResult.success(new TextDecoder().decode(this.response.body)); | ||
parseWithoutBody(responseParser) { | ||
return new HttpOperationResponse(this.response, this.errorHandling, O.none, responseParser); | ||
} | ||
parseWithBodyAsJson() { | ||
const deserializedBodyE = this.jsonSerialization.deserialize(this.response.body); | ||
const result = E.match((error) => OperationHandler_1.OperationHandlerResult.failure(OperationHandler_1.OperationHandlerError.connectorError(`Could not deserialise the body of the response: ${error.message}`)), (deserializedBody) => { | ||
if (this.response.statusCode >= 300) { | ||
return OperationHandler_1.OperationHandlerResult.failure(OperationHandler_1.OperationHandlerError.apiError(`API returned a status code of ${this.response.statusCode}`, { | ||
statusCode: this.response.statusCode, | ||
body: deserializedBody, | ||
})); | ||
} | ||
return OperationHandler_1.OperationHandlerResult.success(deserializedBody); | ||
})(deserializedBodyE); | ||
return result; | ||
parseWithBodyAsText(responseParser) { | ||
return new HttpOperationResponse(this.response, this.errorHandling, O.some(Http_1.HttpContentType.Text), responseParser); | ||
} | ||
parseWithBodyAsJson(responseParser = (body) => OperationHandler_1.OperationHandlerResult.success(body)) { | ||
return new HttpOperationResponse(this.response, this.errorHandling, O.some(Http_1.HttpContentType.Json), responseParser); | ||
} | ||
} | ||
exports.HttpOperationResponseBuilder = HttpOperationResponseBuilder; | ||
class HttpOperationResponse { | ||
constructor(response, errorHandling, contentType, responseParser) { | ||
this.response = response; | ||
this.errorHandling = errorHandling; | ||
this.contentType = contentType; | ||
this.responseParser = responseParser; | ||
} | ||
} | ||
exports.HttpOperationResponse = HttpOperationResponse; | ||
@@ -163,3 +169,3 @@ class HttpOperationHandler { | ||
initialRequest(method, path) { | ||
return new HttpOperationRequest(method, path, { | ||
return new HttpOperationRequestBuilder(method, path, { | ||
headers: { | ||
@@ -166,0 +172,0 @@ [Http_1.HttpHeader.UserAgent]: `TrayioCDK/${packageVersion}`, |
@@ -96,2 +96,8 @@ import { Result, ResultInterface } from '@trayio/commons/result/Result'; | ||
}; | ||
export type FileReference = { | ||
name: string; | ||
url: string; | ||
mime_type: string; | ||
expires: number; | ||
}; | ||
export type OperationHandlerError = ConnectorError | ApiError | UserInputError | OauthRefreshError | SkipTriggerError; | ||
@@ -98,0 +104,0 @@ export type BaseOperationHandlerError = { |
{ | ||
"name": "@trayio/cdk-dsl", | ||
"version": "1.16.0", | ||
"version": "2.0.0", | ||
"description": "A DSL for connector development", | ||
@@ -17,3 +17,3 @@ "exports": { | ||
"dependencies": { | ||
"@trayio/commons": "1.16.0" | ||
"@trayio/commons": "2.0.0" | ||
}, | ||
@@ -20,0 +20,0 @@ "typesVersions": { |
@@ -76,2 +76,3 @@ # Connector Development Kit (CDK) DSL | ||
#### Enums (Static dropdown lists) | ||
Enums are rendered in the Tray builder UI as dropdowns. The user will see the enum display names and the enum values are what will be passed into the handler. By default, user friendly enum display names are generated. e.g. an enum of value `my-enum-value` will be rendered in the UI with the display name `My enum value`. | ||
@@ -90,2 +91,3 @@ | ||
``` | ||
You may want to provide custom enum display names instead, to do so use the JSdoc annotation `@enumLabels` followed by a comma separated list of strings that matches the order you have defined your enums in. | ||
@@ -108,12 +110,13 @@ | ||
#### DDL (Dynamic dropdown lists) | ||
#### DDL (Dynamic dropdown lists) | ||
Sometimes you want to provide the user a dropdown list, but you don't know the items to display in the list because they come from another API. For example you may want to display a list of user names in a dropdown and based on the selection send the user ID. DDL's solve this problem. Once you have created an operation for your DDL (see more on this in the Composite Implementation section) you can use this DDL in the input of many other operations. | ||
The JSdoc annotation `@lookupOperation` specifies what operation to invoke when the user expands the dropdown. `@lookupInput` is used to describe the JSON payload to send to that DDL operation. Any inputs defined in input.ts can be passed to the DDL operation using tripple braces. In this example we send the workspaceId field value by doing `{{{workspaceId}}}`. If your DDL operation calls an authenticated endpoint you can pass along the token used in the current operation by setting `@lookupAuthRequired true`. | ||
```typescript | ||
export type ExampleOperationInput = { | ||
workspaceId: string; | ||
workspaceId: string; | ||
/** | ||
* @title user | ||
* @title user | ||
* @lookupOperation list_users_ddl | ||
@@ -128,2 +131,3 @@ * @lookupInput {"includePrivateChannels": false,"workspaceId": "{{{workspaceId}}}"} | ||
#### Reusing types across multiple operations | ||
You can reuse types by importing them and reuse sections of a schema by using TypeScript intersections. | ||
@@ -159,16 +163,19 @@ | ||
``` | ||
Once the imports and intersection are resolved the above example would look like this | ||
```typescript | ||
export type Input = { | ||
specificField: string; | ||
specificField: string; | ||
reusableFields: { | ||
reusableFieldA: number; | ||
reusableFieldB: string; | ||
}; | ||
reusableSchemaA: number; | ||
reusableFieldA: number; | ||
reusableFieldB: string; | ||
}; | ||
reusableSchemaA: number; | ||
reusableSchemaB: string; | ||
} | ||
}; | ||
``` | ||
#### Union types (Supporting multiple types) | ||
If you want to support two or more different object types in your schema you can achieve this using TypeScript unions. | ||
@@ -179,3 +186,2 @@ | ||
```typescript | ||
export type ExampleOperationInput = { | ||
@@ -191,3 +197,3 @@ elements: ImageOrText[]; | ||
type Image = { | ||
name: string; | ||
name: string; | ||
src: string; | ||
@@ -205,4 +211,5 @@ }; | ||
#### Required/Optional fields | ||
By default all input fields are mandatory, you can set any to optional with a `?` in your TypeScript type. Mandatory fields get a red * in the Tray builder UI and will warn the user that they must be filled in before attempting to run a workflow. | ||
By default all input fields are mandatory, you can set any to optional with a `?` in your TypeScript type. Mandatory fields get a red \* in the Tray builder UI and will warn the user that they must be filled in before attempting to run a workflow. | ||
```typescript | ||
@@ -213,9 +220,10 @@ export type ExampleOperationInput = { | ||
}; | ||
``` | ||
#### Formatting fields | ||
#### Formatting fields | ||
By default properties on your input type render as simple input fields. You can select a more user friendly way to render the field based on your needs by using the JSdoc annotation `@format`. | ||
The format options available are: | ||
- datetime - renders a date and time picker | ||
@@ -243,3 +251,5 @@ - code - renders a button which on click opens a modal with a simple code editor | ||
#### Default values | ||
If you want to provide a default initial value for a field you can use the JSdoc annotation `@default`. | ||
```typescript | ||
@@ -276,2 +286,3 @@ export type ExampleOperationInput = { | ||
#### Advanced fields | ||
If you have optional fields that are for more advanced use cases you can obscure these into the Tray builder UI's advanced fields section. This can provide a cleaner interface for your operation, but can still be accessed by the user by expanding the advanced fields section. You can add fields here by using the JSdoc annotation `@advanced true`. | ||
@@ -289,4 +300,2 @@ | ||
## Handler | ||
@@ -368,5 +377,5 @@ | ||
.handleRequest((ctx, input, request) => | ||
request.addPathParameter('id', input.id.toString()) | ||
request.addPathParameter('id', input.id.toString()).withoutBody() | ||
) | ||
.handleResponse((response) => response.withBodyAsJson()) | ||
.handleResponse((ctx, input, response) => response.parseWithBodyAsJson()) | ||
) | ||
@@ -384,2 +393,3 @@ ); | ||
- `withBodyAsText(body)`: Adds a body to the request that will be sent as a plain text. | ||
- `withoutBody()`: Sends a empty body in the request. | ||
@@ -399,3 +409,3 @@ A handler with an authenticated POST request would look like this: | ||
) | ||
.handleResponse((response) => response.withBodyAsJson()) | ||
.handleResponse((ctx, input, response) => response.parseWithBodyAsJson()) | ||
) | ||
@@ -413,4 +423,7 @@ ); | ||
The `parseWithBodyAsJson<T>() and parseWithBodyAsText()` functions on the `response` argument returns a value of type `OperationHandlerResult<T>`, which can be a successful response or a failure based on the status code or if something went wrong executing the call. | ||
Supported methods on the response configuration are: | ||
- `parseWithBodyAsJson()`: Returns a the requests response as a JSON | ||
- `parseWithBodyAsText((text) => { //logic to transform it to match your output.})`: Returns a requests response as a text to the callback and it can be manipulated to match the operations output schema. | ||
Just like with the input type and the request, the type of the json body in the response can be different from the output type. | ||
@@ -417,0 +430,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
127091
2.77%1139
2.98%690
1.92%+ Added
- Removed
Updated