From c29c6ac5cc1726c755c0c17fb553834e02c7711a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bianchi Date: Fri, 19 Jul 2024 11:28:39 +0200 Subject: [PATCH] Added generic fluent builder proxy Signed-off-by: Jean-Baptiste Bianchi --- README.md | 12 +++---- package-lock.json | 11 ++++-- package.json | 3 +- src/index.ts | 15 -------- src/lib/builder.ts | 58 ++++++++++++++++++++++++++++++ tests/builders/builder.spec.ts | 66 ++++++++++++++++++++++++++++++++++ tsconfig.base.json | 2 +- 7 files changed, 142 insertions(+), 25 deletions(-) delete mode 100644 src/index.ts create mode 100644 src/lib/builder.ts create mode 100644 tests/builders/builder.spec.ts diff --git a/README.md b/README.md index b4d5da1..ae57701 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ npm install && npm run build && npm run test ##### Version >= 4.0.0 Note: Version 4.0.0 has not been released yet. ```sh -npm i @serverless-workflow/sdk +npm i @serverlessworkflow/sdk ``` @@ -55,7 +55,7 @@ npm i @severlessworkflow/sdk-typescript #### Create Workflow using builder API ```typescript -import { workflowBuilder, injectstateBuilder, Specification } from '@serverless-workflow/sdk'; +import { workflowBuilder, injectstateBuilder, Specification } from '@serverlessworkflow/sdk'; const workflow: Specification.Workflow = workflowBuilder() .id("helloworld") @@ -78,7 +78,7 @@ const workflow: Specification.Workflow = workflowBuilder() #### Create Workflow from JSON/YAML source ```typescript -import { Specification, Workflow } from '@serverless-workflow/sdk'; +import { Specification, Workflow } from '@serverlessworkflow/sdk'; const source = `id: helloworld version: '1.0' @@ -102,7 +102,7 @@ Where `source` can be in both JSON or YAML format. Having the following workflow instance: ```typescript -import { workflowBuilder, injectstateBuilder, Specification } from '@serverless-workflow/sdk'; +import { workflowBuilder, injectstateBuilder, Specification } from '@serverlessworkflow/sdk'; const workflow: Specification.Workflow = workflowBuilder() .id("helloworld") @@ -156,7 +156,7 @@ The sdk provides a way to validate if a workflow object is compliant with the se - `validate(): boolean` ```typescript -import {WorkflowValidator, Specification} from '@serverless-workflow/sdk'; +import {WorkflowValidator, Specification} from '@serverlessworkflow/sdk'; import {Workflow} from "./workflow"; const workflow = { @@ -188,7 +188,7 @@ You can also validate parts of a workflow using `validators`: ```typescript import { ValidateFunction } from 'ajv'; -import { validators, Specification } from '@serverless-workflow/sdk'; +import { validators, Specification } from '@serverlessworkflow/sdk'; const injectionState: Specification.Injectstate = workflow.states[0]; const injectionStateValidator: ValidateFunction = validators.get('Injectstate'); diff --git a/package-lock.json b/package-lock.json index 99343c4..fd344cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@serverless-workflow/sdk", + "name": "@serverlessworkflow/sdk", "version": "1.0.0-alpha2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@serverless-workflow/sdk", + "name": "@serverlessworkflow/sdk", "version": "1.0.0-alpha2.0", "license": "http://www.apache.org/licenses/LICENSE-2.0.txt", "dependencies": { @@ -39,6 +39,7 @@ "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.36.0", "shx": "^0.3.4", + "ts-inference-check": "^0.3.0", "ts-jest": "^29.2.2", "ts-morph": "^23.0.0", "ts-node": "^10.9.2", @@ -7052,6 +7053,12 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-inference-check": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ts-inference-check/-/ts-inference-check-0.3.0.tgz", + "integrity": "sha512-nJ0MflAJTFmvJq0bP+KYcPze4WWE+VgkVZt2mL+qiizdqSI/PX55b21q9LpqiSLOOiRPdOWjW5Tsxrrf+jByog==", + "dev": true + }, "node_modules/ts-jest": { "version": "29.2.2", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.2.tgz", diff --git a/package.json b/package.json index f87bdb0..cf902de 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@serverless-workflow/sdk", + "name": "@serverlessworkflow/sdk", "version": "1.0.0-alpha2.0", "schemaVersion": "1.0.0-alpha2", "description": "Typescript SDK for Serverless Workflow Specification", @@ -64,6 +64,7 @@ "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.36.0", "shx": "^0.3.4", + "ts-inference-check": "^0.3.0", "ts-jest": "^29.2.2", "ts-morph": "^23.0.0", "ts-node": "^10.9.2", diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 24aa012..0000000 --- a/src/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2021-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ diff --git a/src/lib/builder.ts b/src/lib/builder.ts new file mode 100644 index 0000000..ec8d0c8 --- /dev/null +++ b/src/lib/builder.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2021-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * oUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Represents a fluent builder proxy + */ +export type Builder = { + build: () => T; +} & { + [K in keyof T]-?: (arg: T[K]) => Builder; +}; + +/** + * The default function used to build an object, basically just return the provided object + * @param data The object to "build" + * @returns + */ +function defaultBuildingFn(data: Partial): () => T { + return () => data as T; +} + +/** + * A factory for fluent builders that proxy properties assignations and can validate against schema on build() + * @param {Function} buildingFn The function used to validate and produce the object on build() + * @returns {Builder} A fluent builder + */ +export function builder(buildingFn?: (data: Partial) => () => T): Builder { + const data: Partial = {}; + const proxy = new Proxy({} as Builder, { + get: (_, prop) => { + if (prop === 'build') { + return (buildingFn || defaultBuildingFn)(data); + } + return (value: unknown): Builder => { + (data as any)[prop.toString()] = value; + return proxy; + }; + }, + set: () => { + return false; + }, + }); + return proxy; +} diff --git a/tests/builders/builder.spec.ts b/tests/builders/builder.spec.ts new file mode 100644 index 0000000..3f5f9bb --- /dev/null +++ b/tests/builders/builder.spec.ts @@ -0,0 +1,66 @@ +/* + * Copyright 2021-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * oUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Builder, builder } from '../../src/lib/builder'; +import { type } from 'ts-inference-check'; + +type Person = { + name: string; + age: number; + friends?: Array; + [k: string]: unknown; +}; + +const darknessMyOldFriend = { name: 'Darkness', age: 999 }; +const isPerson = (data: Partial): data is Person => !!data.name && !!data.age; +function personBuildingFn(data: Partial): () => Person { + return () => { + if (!isPerson(data)) { + throw new Error('The provided object is not a person'); + } + return { + ...data, + friends: [...(data.friends || []), darknessMyOldFriend], + }; + }; +} +const personBuilder = (): Builder => builder(personBuildingFn); + +describe('builder proxy', () => { + it('should infer property types', () => { + const builder = personBuilder(); + expect(type(builder.name).is<(arg: string) => Builder>(true)).toBe(true); + expect(type(builder.age).is<(arg: number) => Builder>(true)).toBe(true); + expect(type(builder.friends).is<(arg: Array | undefined) => Builder>(true)).toBe(true); + expect(type(builder.lover).is<(arg: unknown) => Builder>(true)).toBe(true); + }); + + it('should build', () => { + const name = 'John Doe'; + const age = 42; + const friend = { name: 'Cookie Doe', age: 42 }; + const lover = 'Jane Doe'; + const person = personBuilder().name(name).age(age).friends([friend]).lover(lover).build(); + expect(person).toBeDefined(); + expect(person.name).toBe(name); + expect(person.age).toBe(age); + expect(person.friends?.length).toBe(2); + expect(person.friends?.includes(friend)).toBe(true); + expect(person.friends?.includes(darknessMyOldFriend)).toBe(true); + expect(person.lover).toBe(lover); + }); +}); diff --git a/tsconfig.base.json b/tsconfig.base.json index 7e84fbd..c46f6ce 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "lib": [ "ES2019", "DOM" ], /* Specify library files to be included in the compilation. */ "declaration": true, /* Generates corresponding '.d.ts' file. */