diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba7efe90..aa3d0a48 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ concurrency: jobs: - build-all: + build: runs-on: macos-latest @@ -34,7 +34,6 @@ jobs: - name: Run run: | make build - make example - name: Archive linux cli uses: actions/upload-artifact@v3 with: @@ -61,6 +60,27 @@ jobs: name: wirespec-intellij-plugin path: src/ide/intellij-plugin/build/distributions/intellij-plugin-*.zip + example: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + cache: gradle + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + - uses: actions/setup-node@v3 + with: + node-version: 20 + - name: Run + run: | + make example + version: runs-on: ubuntu-latest @@ -68,7 +88,8 @@ jobs: if: github.event_name == 'release' && github.event.action == 'created' needs: - - build-all + - build + - example outputs: version: ${{steps.version.outputs.version}} @@ -204,9 +225,7 @@ jobs: :src:plugin:gradle:publish \ :src:plugin:maven:publish \ :src:plugin:arguments:publish \ - :src:integration:wirespec:publish \ :src:integration:jackson:publish \ - :src:integration:spring:publish \ :src:tools:generator:publish release-lib-npm: @@ -239,4 +258,4 @@ jobs: - name: Build working-directory: ./src/plugin/npm/build/dist/js/productionLibrary run: | - npm publish --access public + npm publish --access public \ No newline at end of file diff --git a/examples/npm-typescript/src/clientProxy.ts b/examples/npm-typescript/src/clientProxy.ts index 51fe4130..012ef53e 100644 --- a/examples/npm-typescript/src/clientProxy.ts +++ b/examples/npm-typescript/src/clientProxy.ts @@ -1,6 +1,6 @@ -import { GetTodoById, GetTodos, PostTodo, Wirespec } from "./gen/Todo"; +import {GetTodoById, GetTodos, PostTodo, Wirespec} from "./gen/Todo"; +import {GetUsers} from "./gen/User"; import * as assert from "node:assert"; -import Client = Wirespec.Client; const serialization: Wirespec.Serialization = { deserialize(raw: string | undefined): T { @@ -38,15 +38,17 @@ const mocks = [ mock("POST", ["api", "todos"], 200, {}, JSON.stringify({ id: "3", name: "Do more", done: true })) ]; -type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; -type Api = Wirespec.Api, Wirespec.Response, Han> -type WebClient = []>(...apis: Apis) => UnionToIntersection ? Han : never>; +type ApiClient = (req: REQ) => Promise; +type WebClient = , Wirespec.Response>[]>(...apis: Apis) => { + [K in Apis[number]['name']]: Extract extends Wirespec.Api ? + ApiClient : never +}; const webClient:WebClient = (...apis) => { - const activeClients:Record, Wirespec.Response, any>>> = apis.reduce((acc, cur) => ({...acc, [cur.name] : cur.client(serialization)}), {}) const proxy = new Proxy({}, { get: (_, prop) => { - const client = activeClients[prop as keyof typeof activeClients]; + const api = apis.find(it => it.name === prop); + const client = api.client(serialization); return (req:Wirespec.Request) => { const rawRequest = client.to(req); const rawResponse: Wirespec.RawResponse = mocks.find(it => @@ -56,12 +58,13 @@ const webClient:WebClient = (...apis) => { assert.notEqual(rawResponse, undefined); return Promise.resolve(client.from(rawResponse)); } + }, }); - return proxy as ReturnType + return proxy as any } -const api = webClient(PostTodo.api, GetTodos.api, GetTodoById.api) +const api = webClient(PostTodo.api, GetTodos.api, GetTodoById.api, GetUsers.api) const testGetTodos = async () => { const request: GetTodos.Request = GetTodos.request(); diff --git a/examples/npm-typescript/src/clientSimple.ts b/examples/npm-typescript/src/clientSimple.ts index 5bb2f115..ac4079a2 100644 --- a/examples/npm-typescript/src/clientSimple.ts +++ b/examples/npm-typescript/src/clientSimple.ts @@ -28,7 +28,7 @@ type Api = GetTodoById.Handler & PostTodo.Handler -const handleFetch = , Res extends Wirespec.Response>(client: Wirespec.Client) => (request: Req): Promise => { +const handleFetch = , Res extends Wirespec.Response>(client: Wirespec.Client) => (request: Req): Promise => { const mock = (method: Wirespec.Method, path: string[], status: number, headers: Record, body: any) => ({ method, path, diff --git a/examples/npm-typescript/wirespec/user.ws b/examples/npm-typescript/wirespec/user.ws new file mode 100644 index 00000000..b694257b --- /dev/null +++ b/examples/npm-typescript/wirespec/user.ws @@ -0,0 +1,13 @@ +type User { + name: String +} + +type Error { + code: Integer, + description: String +} + +endpoint GetUsers GET /api/users -> { + 200 -> User[] +} + diff --git a/scripts/example.sh b/scripts/example.sh index 8f561f66..d27320c4 100755 --- a/scripts/example.sh +++ b/scripts/example.sh @@ -1,13 +1,12 @@ -#!/usr/bin/env bash - dir="$(dirname -- "$0")" -./gradlew src:compiler:core:publishToMavenLocal && -./gradlew src:integration:wirespec:publishToMavenLocal && -./gradlew src:integration:jackson:publishToMavenLocal && -./gradlew src:integration:spring:publishToMavenLocal && -./gradlew jvmTest && -./gradlew src:plugin:gradle:publishToMavenLocal && -./gradlew src:plugin:maven:publishToMavenLocal && +./gradlew \ + src:converter:openapi:jvmJar \ + src:plugin:arguments:jvmJar \ + src:integration:jackson:publishToMavenLocal \ + src:integration:spring:publishToMavenLocal \ + src:plugin:gradle:publishToMavenLocal \ + src:plugin:maven:publishToMavenLocal \ + src:plugin:npm:jsNodeProductionLibraryDistribution && (cd "$dir"/../src/ide/vscode && npm i && npm run build) && -(cd "$dir"/../examples && make clean && make build) +(cd "$dir"/../examples && make clean && make build) \ No newline at end of file diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/TypeScriptEmitter.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/TypeScriptEmitter.kt index 7930b6bc..6863d524 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/TypeScriptEmitter.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/TypeScriptEmitter.kt @@ -103,17 +103,23 @@ open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter |${Spacer}} |${endpoint.emitClient().prependIndent(Spacer(1))} |${endpoint.emitServer().prependIndent(Spacer(1))} - |${Spacer}export const api: Wirespec.Api = { + |${Spacer}export const api = { |${Spacer(2)}name: "${endpoint.identifier.sanitizeSymbol().firstToLower()}", |${Spacer(2)}method: "${endpoint.method.name}", |${Spacer(2)}path: "${endpoint.path.joinToString("/") { it.emit() }}", |${Spacer(2)}server, |${Spacer(2)}client - |${Spacer}} + |${Spacer}} as const |} | """.trimMargin() + override fun Endpoint.Segment.emit() = + when (this) { + is Endpoint.Segment.Literal -> value + is Endpoint.Segment.Param -> ":${identifier.value}" + } + private fun emitHandleFunction(endpoint: Endpoint) = "${endpoint.identifier.sanitizeSymbol().firstToLower()}: (request:Request) => Promise" @@ -187,7 +193,7 @@ open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter .joinToString("") private fun Endpoint.emitClient() = """ - |export const client: Wirespec.Client = (serialization: Wirespec.Serialization) => ({ + |export const client: Wirespec.Client = (serialization: Wirespec.Serialization) => ({ |${emitClientTo().prependIndent(Spacer(1))}, |${emitClientFrom().prependIndent(Spacer(1))} |}) @@ -230,7 +236,7 @@ open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter """.trimMargin() private fun Endpoint.emitServer() = """ - |export const server:Wirespec.Server = (serialization: Wirespec.Serialization) => ({ + |export const server:Wirespec.Server = (serialization: Wirespec.Serialization) => ({ |${emitServerFrom().prependIndent(Spacer(1))}, |${emitServerTo().prependIndent(Spacer(1))} |}) diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitter.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitter.kt index ec913795..37321981 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitter.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitter.kt @@ -61,7 +61,7 @@ abstract class Emitter( } } - fun Endpoint.Segment.emit() = + open fun Endpoint.Segment.emit() = when (this) { is Endpoint.Segment.Literal -> value is Endpoint.Segment.Param -> "{${identifier.value}}" diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/shared/TypeScriptShared.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/shared/TypeScriptShared.kt index 88d6e875..f5841191 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/shared/TypeScriptShared.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/shared/TypeScriptShared.kt @@ -15,9 +15,9 @@ data object TypeScriptShared : Shared { |${Spacer}export type Request = { path: Record, method: Method, query?: Record, headers?: Record, content?:Content } |${Spacer}export type Response = { status:number, headers?: Record, content?:Content } |${Spacer}export type Serialization = { serialize: (type: T) => string; deserialize: (raw: string | undefined) => T } - |${Spacer}export type Client, RES extends Response, HAN> = (serialization: Serialization) => { to: (request: REQ) => RawRequest; from: (response: RawResponse) => RES } - |${Spacer}export type Server, RES extends Response, HAN> = (serialization: Serialization) => { from: (request: RawRequest) => REQ; to: (response: RES) => RawResponse } - |${Spacer}export type Api, RES extends Response, HAN> = { name: string; method: Method, path: string, client: Client; server: Server } + |${Spacer}export type Client, RES extends Response> = (serialization: Serialization) => { to: (request: REQ) => RawRequest; from: (response: RawResponse) => RES } + |${Spacer}export type Server, RES extends Response> = (serialization: Serialization) => { from: (request: RawRequest) => REQ; to: (response: RES) => RawResponse } + |${Spacer}export type Api, RES extends Response> = { name: string; method: Method, path: string, client: Client; server: Server } |} """.trimMargin() } diff --git a/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileFullEndpointTest.kt b/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileFullEndpointTest.kt index f90d83c8..f72febd0 100644 --- a/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileFullEndpointTest.kt +++ b/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileFullEndpointTest.kt @@ -340,9 +340,9 @@ class CompileFullEndpointTest { | export type Request = { path: Record, method: Method, query?: Record, headers?: Record, content?:Content } | export type Response = { status:number, headers?: Record, content?:Content } | export type Serialization = { serialize: (type: T) => string; deserialize: (raw: string | undefined) => T } - | export type Client, RES extends Response, HAN> = (serialization: Serialization) => { to: (request: REQ) => RawRequest; from: (response: RawResponse) => RES } - | export type Server, RES extends Response, HAN> = (serialization: Serialization) => { from: (request: RawRequest) => REQ; to: (response: RES) => RawResponse } - | export type Api, RES extends Response, HAN> = { name: string; method: Method, path: string, client: Client; server: Server } + | export type Client, RES extends Response> = (serialization: Serialization) => { to: (request: REQ) => RawRequest; from: (response: RawResponse) => RES } + | export type Server, RES extends Response> = (serialization: Serialization) => { from: (request: RawRequest) => REQ; to: (response: RES) => RawResponse } + | export type Api, RES extends Response> = { name: string; method: Method, path: string, client: Client; server: Server } |} |export namespace PutTodo { | type Path = { @@ -392,7 +392,7 @@ class CompileFullEndpointTest { | export type Handler = { | putTodo: (request:Request) => Promise | } - | export const client: Wirespec.Client = (serialization: Wirespec.Serialization) => ({ + | export const client: Wirespec.Client = (serialization: Wirespec.Serialization) => ({ | to: (request) => ({ | method: "PUT", | path: ["todos", serialization.serialize(request.path.id)], @@ -419,7 +419,7 @@ class CompileFullEndpointTest { | } | } | }) - | export const server:Wirespec.Server = (serialization: Wirespec.Serialization) => ({ + | export const server:Wirespec.Server = (serialization: Wirespec.Serialization) => ({ | from: (request) => { | return { | method: "PUT", @@ -441,13 +441,13 @@ class CompileFullEndpointTest { | body: serialization.serialize(response.body), | }) | }) - | export const api: Wirespec.Api = { + | export const api = { | name: "putTodo", | method: "PUT", - | path: "todos/{id}", + | path: "todos/:id", | server, | client - | } + | } as const |} | |export type PotentialTodoDto = { diff --git a/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileMinimalEndpointTest.kt b/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileMinimalEndpointTest.kt index df59f033..18164588 100644 --- a/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileMinimalEndpointTest.kt +++ b/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileMinimalEndpointTest.kt @@ -238,9 +238,9 @@ class CompileMinimalEndpointTest { | export type Request = { path: Record, method: Method, query?: Record, headers?: Record, content?:Content } | export type Response = { status:number, headers?: Record, content?:Content } | export type Serialization = { serialize: (type: T) => string; deserialize: (raw: string | undefined) => T } - | export type Client, RES extends Response, HAN> = (serialization: Serialization) => { to: (request: REQ) => RawRequest; from: (response: RawResponse) => RES } - | export type Server, RES extends Response, HAN> = (serialization: Serialization) => { from: (request: RawRequest) => REQ; to: (response: RES) => RawResponse } - | export type Api, RES extends Response, HAN> = { name: string; method: Method, path: string, client: Client; server: Server } + | export type Client, RES extends Response> = (serialization: Serialization) => { to: (request: REQ) => RawRequest; from: (response: RawResponse) => RES } + | export type Server, RES extends Response> = (serialization: Serialization) => { from: (request: RawRequest) => REQ; to: (response: RES) => RawResponse } + | export type Api, RES extends Response> = { name: string; method: Method, path: string, client: Client; server: Server } |} |export namespace GetTodos { | type Path = {} @@ -274,7 +274,7 @@ class CompileMinimalEndpointTest { | export type Handler = { | getTodos: (request:Request) => Promise | } - | export const client: Wirespec.Client = (serialization: Wirespec.Serialization) => ({ + | export const client: Wirespec.Client = (serialization: Wirespec.Serialization) => ({ | to: (request) => ({ | method: "GET", | path: ["todos"], @@ -295,7 +295,7 @@ class CompileMinimalEndpointTest { | } | } | }) - | export const server:Wirespec.Server = (serialization: Wirespec.Serialization) => ({ + | export const server:Wirespec.Server = (serialization: Wirespec.Serialization) => ({ | from: (request) => { | return { | method: "GET", @@ -317,13 +317,13 @@ class CompileMinimalEndpointTest { | body: serialization.serialize(response.body), | }) | }) - | export const api: Wirespec.Api = { + | export const api = { | name: "getTodos", | method: "GET", | path: "todos", | server, | client - | } + | } as const |} | |export type TodoDto = {