diff --git a/_examples/node-ts-header/Makefile b/_examples/node-ts-header/Makefile new file mode 100644 index 0000000..321e438 --- /dev/null +++ b/_examples/node-ts-header/Makefile @@ -0,0 +1,2 @@ +generate: + webrpc-gen -schema=example.ridl -target=../../../gen-typescript -client -server -out=./example.gen.ts diff --git a/_examples/node-ts-header/example.gen.ts b/_examples/node-ts-header/example.gen.ts new file mode 100644 index 0000000..fa2e965 --- /dev/null +++ b/_examples/node-ts-header/example.gen.ts @@ -0,0 +1,710 @@ +/* eslint-disable */ +// header-example v0.0.1 21bfb75b73f488bc95ebb40491f03a5d29060015 +// -- +// Code generated by Webrpc-gen@v0.25.0-4-g18056cf with ../../ generator. DO NOT EDIT. +// +// webrpc-gen -schema=example.ridl -target=../../ -client -server -out=./example.gen.ts + +// Webrpc description and code-gen version +export const WebrpcVersion = "v1" + +// Schema version of your RIDL schema +export const WebrpcSchemaVersion = "v0.0.1" + +// Schema hash generated from your RIDL schema +export const WebrpcSchemaHash = "21bfb75b73f488bc95ebb40491f03a5d29060015" + +// +// Client interface +// + +export interface ExampleServiceClient { + /** + * Simple method, no headers + */ + ping(headers?: object, signal?: AbortSignal): Promise + + /** + * Single header field (authToken sent as HTTP header, userID in body) + */ + getUser(req: GetUserRequest, headers?: object, signal?: AbortSignal): Promise + + /** + * Multiple header fields (authToken and role as headers, username in body) + */ + createUser(req: CreateUserRequest, headers?: object, signal?: AbortSignal): Promise + + /** + * All fields as headers (no body) + */ + deleteUser(req: DeleteUserRequest, headers?: object, signal?: AbortSignal): Promise +} + +// +// Server interface +// + +export interface ExampleServiceServer { + ping(ctx: Context, req: PingRequest): Promise + getUser(ctx: Context, req: GetUserRequest): Promise + createUser(ctx: Context, req: CreateUserRequest): Promise + deleteUser(ctx: Context, req: DeleteUserRequest): Promise +} + +// +// Schema types +// + +export interface User { + id: number + username: string +} + +export interface GetUserRequest { + authToken: string + userID: number +} + +export interface GetUserResponse { + user: User +} + +export interface CreateUserRequest { + authToken: string + role: string + username: string +} + +export interface CreateUserResponse { + user: User +} + +export interface DeleteUserRequest { + authToken: string + userID: number +} + +export interface PingRequest { +} + +export interface PingResponse { +} + + + + + + +// +// Client +// + +export class ExampleService implements ExampleServiceClient { + protected hostname: string + protected fetch: Fetch + protected path = '/rpc/ExampleService/' + + constructor(hostname: string, fetch: Fetch) { + this.hostname = hostname.replace(/\/*$/, '') + this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) + } + + private url(name: string): string { + return this.hostname + this.path + name + } + + queryKey = { + ping: () => ['ExampleService', 'ping'] as const, + getUser: (req: GetUserRequest) => ['ExampleService', 'getUser', req] as const, + createUser: (req: CreateUserRequest) => ['ExampleService', 'createUser', req] as const, + deleteUser: (req: DeleteUserRequest) => ['ExampleService', 'deleteUser', req] as const, + } + + ping = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('Ping'), + createHttpRequest('{}', headers, signal)).then((res) => { + return buildResponse(res).then(_data => { + return JsonDecode(_data, 'PingResponse') + }) + }, (error) => { + throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}` }) + }) + } + + getUser = (req: GetUserRequest, headers?: object, signal?: AbortSignal): Promise => { + const _headers: Record = { ...((headers || {}) as Record) } + if (req.authToken != null) { + _headers["authToken"] = String(req.authToken) + } + return this.fetch( + this.url('GetUser'), + createHttpRequest(JsonEncode((() => { const {authToken: _h0, ..._body} = req; return _body})()), _headers, signal)).then((res) => { + return buildResponse(res).then(_data => { + return JsonDecode(_data, 'GetUserResponse') + }) + }, (error) => { + throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}` }) + }) + } + + createUser = (req: CreateUserRequest, headers?: object, signal?: AbortSignal): Promise => { + const _headers: Record = { ...((headers || {}) as Record) } + if (req.authToken != null) { + _headers["authToken"] = String(req.authToken) + } + if (req.role != null && req.role !== "") { + _headers["role"] = String(req.role) + } + return this.fetch( + this.url('CreateUser'), + createHttpRequest(JsonEncode((() => { const {authToken: _h0, role: _h1, ..._body} = req; return _body})()), _headers, signal)).then((res) => { + return buildResponse(res).then(_data => { + return JsonDecode(_data, 'CreateUserResponse') + }) + }, (error) => { + throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}` }) + }) + } + + deleteUser = (req: DeleteUserRequest, headers?: object, signal?: AbortSignal): Promise => { + const _headers: Record = { ...((headers || {}) as Record) } + if (req.authToken != null) { + _headers["authToken"] = String(req.authToken) + } + if (req.userID != null) { + _headers["userID"] = String(req.userID) + } + return this.fetch( + this.url('DeleteUser'), + createHttpRequest(JsonEncode({}), _headers, signal)).then((res) => { + return buildResponse(res).then(_data => { + }) + }, (error) => { + throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}` }) + }) + } + +} + +const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { + const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json', [WebrpcHeader]: WebrpcHeaderValue } + return { method: 'POST', headers: reqHeaders, body, signal } +} + +const buildResponse = (res: Response): Promise => { + return res.text().then(text => { + let data + try { + data = JSON.parse(text) + } catch(error) { + throw WebrpcBadResponseError.new({ + status: res.status, + cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`}, + ) + } + if (!res.ok) { + const code: number = (typeof data.code === 'number') ? data.code : 0 + throw (webrpcErrorByCode[code] || WebrpcError).new(data) + } + return data + }) +} + +export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise + + + + +// +// Server handler +// + +export const serveExampleServiceRpc = async (service: ExampleServiceServer, ctx: Context, urlPath: string, body: any, reqHeaders?: Record) => { + if (!urlPath.startsWith('/rpc/')) return null + const parts = urlPath.split('/').filter(Boolean) + if (parts.length !== 3 || parts[0] !== 'rpc' || parts[1] !== 'ExampleService') return null + const method = parts[2]! + try { + const result = await dispatchExampleServiceRequest(service, ctx, method, body, reqHeaders) + return { + method, + status: 200, + headers: { [WebrpcHeader]: WebrpcHeaderValue, 'Content-Type': 'application/json' }, + body: result ?? {} + } + } catch (err: any) { + if (err instanceof WebrpcError) { + const status = err.status || 400 + return { + method, + status, + headers: { [WebrpcHeader]: WebrpcHeaderValue, 'Content-Type': 'application/json' }, + body: err + } + } else { + return { + method, + status: 400, + headers: { [WebrpcHeader]: WebrpcHeaderValue, 'Content-Type': 'application/json' }, + body: new WebrpcError({ message: err?.message }) + } + } + } +} + +const dispatchExampleServiceRequest = async (service: ExampleServiceServer, ctx: Context, method: string, body: any, reqHeaders?: Record) => { + let payload: any = body + let result: any + switch (method) { + case 'Ping': + result = await service.ping(ctx, payload || {}) + break + + case 'GetUser': + { + const args: any = { ...(payload || {}) } + args["authToken"] = reqHeaders?.["authToken"] ?? reqHeaders?.["authtoken"] ?? "" + result = await service.getUser(ctx, args) + } + break + + case 'CreateUser': + { + const args: any = { ...(payload || {}) } + args["authToken"] = reqHeaders?.["authToken"] ?? reqHeaders?.["authtoken"] ?? "" + args["role"] = reqHeaders?.["role"] ?? reqHeaders?.["role"] ?? "" + result = await service.createUser(ctx, args) + } + break + + case 'DeleteUser': + { + const args: any = { ...(payload || {}) } + args["authToken"] = reqHeaders?.["authToken"] ?? reqHeaders?.["authtoken"] ?? "" + { + const _v = reqHeaders?.["userID"] ?? reqHeaders?.["userid"] + if (_v && _v !== "") { + const _n = Number(_v) + if (Number.isNaN(_n)) { + throw new WebrpcBadRequestError({ cause: "invalid header argument: userID" }) + } + args["userID"] = _n + } else { + args["userID"] = 0 + } + } + result = await service.deleteUser(ctx, args) + } + break + + default: + throw new WebrpcBadRouteError({ cause: 'method not found' }) + } + return result +} + + + +// +// Server type validators +// + +const JS_TYPES = [ + "bigint", + "boolean", + "function", + "number", + "object", + "string", + "symbol", + "undefined" +] + +const validateUser = (value: any) => { + if (!("id" in value) || !validateType(value["id"], "number")) { + return false + } + if (!("username" in value) || !validateType(value["username"], "string")) { + return false + } + return true +} + +const validateGetUserRequest = (value: any) => { + if (!("authToken" in value) || !validateType(value["authToken"], "string")) { + return false + } + if (!("userID" in value) || !validateType(value["userID"], "number")) { + return false + } + return true +} + +const validateGetUserResponse = (value: any) => { + if (!("user" in value) || !validateType(value["user"], "User")) { + return false + } + return true +} + +const validateCreateUserRequest = (value: any) => { + if (!("authToken" in value) || !validateType(value["authToken"], "string")) { + return false + } + if (!("role" in value) || !validateType(value["role"], "string")) { + return false + } + if (!("username" in value) || !validateType(value["username"], "string")) { + return false + } + return true +} + +const validateCreateUserResponse = (value: any) => { + if (!("user" in value) || !validateType(value["user"], "User")) { + return false + } + return true +} + +const validateDeleteUserRequest = (value: any) => { + if (!("authToken" in value) || !validateType(value["authToken"], "string")) { + return false + } + if (!("userID" in value) || !validateType(value["userID"], "number")) { + return false + } + return true +} + +const TYPE_VALIDATORS: { [type: string]: (value: any) => boolean } = { + User: validateUser, + GetUserRequest: validateGetUserRequest, + GetUserResponse: validateGetUserResponse, + CreateUserRequest: validateCreateUserRequest, + CreateUserResponse: validateCreateUserResponse, + DeleteUserRequest: validateDeleteUserRequest, +} + +const validateType = (value: any, type: string) => { + if (JS_TYPES.indexOf(type) > -1) { + return typeof value === type; + } + const validator = TYPE_VALIDATORS[type]; + if (!validator) { + return false; + } + return validator(value) +} + + +export const JsonEncode = (obj: T): string => { + return JSON.stringify(obj) +} + +export const JsonDecode = (data: string | any, _typ: string = ''): T => { + let parsed: any = data + if (typeof data === 'string') { + try { parsed = JSON.parse(data) } catch (err) { + throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) + } + } + return parsed as T +} + + +// +// Errors +// + +type WebrpcErrorParams = { name?: string, code?: number, message?: string, status?: number, cause?: string } + +export class WebrpcError extends Error { + code: number + status: number + + constructor(error: WebrpcErrorParams = {}) { + super(error.message) + this.name = error.name || 'WebrpcEndpointError' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcError.prototype) + } + + static new(payload: any): WebrpcError { + return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) + } +} + + +export class WebrpcEndpointError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcEndpoint' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcEndpointError.prototype) + } +} + +export class WebrpcRequestFailedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcRequestFailed' + this.code = typeof error.code === 'number' ? error.code : -1 + this.message = error.message || `request failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) + } +} + +export class WebrpcBadRouteError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRoute' + this.code = typeof error.code === 'number' ? error.code : -2 + this.message = error.message || `bad route` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) + } +} + +export class WebrpcBadMethodError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadMethod' + this.code = typeof error.code === 'number' ? error.code : -3 + this.message = error.message || `bad method` + this.status = typeof error.status === 'number' ? error.status : 405 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) + } +} + +export class WebrpcBadRequestError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRequest' + this.code = typeof error.code === 'number' ? error.code : -4 + this.message = error.message || `bad request` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) + } +} + +export class WebrpcBadResponseError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadResponse' + this.code = typeof error.code === 'number' ? error.code : -5 + this.message = error.message || `bad response` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) + } +} + +export class WebrpcServerPanicError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcServerPanic' + this.code = typeof error.code === 'number' ? error.code : -6 + this.message = error.message || `server panic` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) + } +} + +export class WebrpcInternalErrorError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcInternalError' + this.code = typeof error.code === 'number' ? error.code : -7 + this.message = error.message || `internal error` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) + } +} + +export class WebrpcClientAbortedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcClientAborted' + this.code = typeof error.code === 'number' ? error.code : -8 + this.message = error.message || `request aborted by client` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) + } +} + +export class WebrpcStreamLostError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamLost' + this.code = typeof error.code === 'number' ? error.code : -9 + this.message = error.message || `stream lost` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) + } +} + +export class WebrpcStreamFinishedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamFinished' + this.code = typeof error.code === 'number' ? error.code : -10 + this.message = error.message || `stream finished` + this.status = typeof error.status === 'number' ? error.status : 200 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) + } +} + + +// +// Schema errors +// + +export class UnauthorizedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unauthorized' + this.code = typeof error.code === 'number' ? error.code : 400100 + this.message = error.message || `unauthorized` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, UnauthorizedError.prototype) + } +} + +export class UserNotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'UserNotFound' + this.code = typeof error.code === 'number' ? error.code : 400200 + this.message = error.message || `user not found` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, UserNotFoundError.prototype) + } +} + + +export enum errors { + WebrpcEndpoint = 'WebrpcEndpoint', + WebrpcRequestFailed = 'WebrpcRequestFailed', + WebrpcBadRoute = 'WebrpcBadRoute', + WebrpcBadMethod = 'WebrpcBadMethod', + WebrpcBadRequest = 'WebrpcBadRequest', + WebrpcBadResponse = 'WebrpcBadResponse', + WebrpcServerPanic = 'WebrpcServerPanic', + WebrpcInternalError = 'WebrpcInternalError', + WebrpcClientAborted = 'WebrpcClientAborted', + WebrpcStreamLost = 'WebrpcStreamLost', + WebrpcStreamFinished = 'WebrpcStreamFinished', + Unauthorized = 'Unauthorized', + UserNotFound = 'UserNotFound', +} + +export enum WebrpcErrorCodes { + WebrpcEndpoint = 0, + WebrpcRequestFailed = -1, + WebrpcBadRoute = -2, + WebrpcBadMethod = -3, + WebrpcBadRequest = -4, + WebrpcBadResponse = -5, + WebrpcServerPanic = -6, + WebrpcInternalError = -7, + WebrpcClientAborted = -8, + WebrpcStreamLost = -9, + WebrpcStreamFinished = -10, + Unauthorized = 400100, + UserNotFound = 400200, +} + +export const webrpcErrorByCode: { [code: number]: any } = { + [0]: WebrpcEndpointError, + [-1]: WebrpcRequestFailedError, + [-2]: WebrpcBadRouteError, + [-3]: WebrpcBadMethodError, + [-4]: WebrpcBadRequestError, + [-5]: WebrpcBadResponseError, + [-6]: WebrpcServerPanicError, + [-7]: WebrpcInternalErrorError, + [-8]: WebrpcClientAbortedError, + [-9]: WebrpcStreamLostError, + [-10]: WebrpcStreamFinishedError, + [400100]: UnauthorizedError, + [400200]: UserNotFoundError, +} + + + +// +// Webrpc +// + +export const WebrpcHeader = "Webrpc" + +export const WebrpcHeaderValue = "webrpc@v0.25.0-4-g18056cf;@unknown;header-example@v0.0.1" + +type WebrpcGenVersions = { + WebrpcGenVersion: string; + codeGenName: string; + codeGenVersion: string; + schemaName: string; + schemaVersion: string; +}; + +export function VersionFromHeader(headers: Headers): WebrpcGenVersions { + const headerValue = headers.get(WebrpcHeader) + if (!headerValue) { + return { + WebrpcGenVersion: "", + codeGenName: "", + codeGenVersion: "", + schemaName: "", + schemaVersion: "", + }; + } + + return parseWebrpcGenVersions(headerValue) +} + +function parseWebrpcGenVersions(header: string): WebrpcGenVersions { + const versions = header.split(";") + if (versions.length < 3) { + return { + WebrpcGenVersion: "", + codeGenName: "", + codeGenVersion: "", + schemaName: "", + schemaVersion: "", + }; + } + + const [_, WebrpcGenVersion] = versions[0]!.split("@") + const [codeGenName, codeGenVersion] = versions[1]!.split("@") + const [schemaName, schemaVersion] = versions[2]!.split("@") + + return { + WebrpcGenVersion: WebrpcGenVersion ?? "", + codeGenName: codeGenName ?? "", + codeGenVersion: codeGenVersion ?? "", + schemaName: schemaName ?? "", + schemaVersion: schemaVersion ?? "", + }; +} + diff --git a/_examples/node-ts-header/example.ridl b/_examples/node-ts-header/example.ridl new file mode 100644 index 0000000..a25e7b2 --- /dev/null +++ b/_examples/node-ts-header/example.ridl @@ -0,0 +1,49 @@ +webrpc = v1 + +name = header-example +version = v0.0.1 +basepath = /rpc + +struct User + - id: uint64 + - username: string + +error 400100 Unauthorized "unauthorized" HTTP 401 +error 400200 UserNotFound "user not found" + +struct GetUserRequest + - authToken: string + + location = header + - userID: uint64 + +struct GetUserResponse + - user: User + +struct CreateUserRequest + - authToken: string + + location = header + - role: string + + location = header,omitempty + - username: string + +struct CreateUserResponse + - user: User + +struct DeleteUserRequest + - authToken: string + + location = header + - userID: uint64 + + location = header + +service ExampleService + # Simple method, no headers + - Ping() + + # Single header field (authToken sent as HTTP header, userID in body) + - GetUser(GetUserRequest) => (GetUserResponse) + + # Multiple header fields (authToken and role as headers, username in body) + - CreateUser(CreateUserRequest) => (CreateUserResponse) + + # All fields as headers (no body) + - DeleteUser(DeleteUserRequest) diff --git a/_examples/node-ts-header/example.test.ts b/_examples/node-ts-header/example.test.ts new file mode 100644 index 0000000..70de24f --- /dev/null +++ b/_examples/node-ts-header/example.test.ts @@ -0,0 +1,48 @@ +import http from 'node:http' +import { describe, it, expect, beforeAll, afterAll } from 'vitest' +import { ExampleService, UnauthorizedError } from './example.gen.js' +import { startServer } from './server.js' + +let server: http.Server +let client: ExampleService + +const PORT = 4244 + +beforeAll(async () => { + server = await startServer(PORT) + const fetch_ = (input: RequestInfo, init?: RequestInit) => fetch(input, init) + client = new ExampleService(`http://localhost:${PORT}`, fetch_) +}) + +afterAll(() => { + server?.close() +}) + +describe('Header example', () => { + it('Ping - no headers', async () => { + await expect(client.ping()).resolves.not.toThrow() + }) + + it('GetUser - authToken as header, userID in body', async () => { + const resp = await client.getUser({ authToken: 'my-secret-token', userID: 42 }) + expect(resp.user.id).toBe(42) + expect(resp.user.username).toBe('alice') + }) + + it('GetUser - missing token returns Unauthorized', async () => { + await expect(client.getUser({ authToken: '', userID: 42 })).rejects.toThrow() + }) + + it('CreateUser - multiple headers (authToken, role), username in body', async () => { + const resp = await client.createUser({ authToken: 'my-secret-token', role: 'admin', username: 'bob' }) + expect(resp.user.username).toBe('bob') + }) + + it('DeleteUser - all params as headers, no body', async () => { + await expect(client.deleteUser({ authToken: 'my-secret-token', userID: 99 })).resolves.not.toThrow() + }) + + it('DeleteUser - missing token returns Unauthorized', async () => { + await expect(client.deleteUser({ authToken: '', userID: 99 })).rejects.toThrow() + }) +}) diff --git a/_examples/node-ts-header/package-lock.json b/_examples/node-ts-header/package-lock.json new file mode 100644 index 0000000..7337467 --- /dev/null +++ b/_examples/node-ts-header/package-lock.json @@ -0,0 +1,1431 @@ +{ + "name": "node-ts-header-example", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node-ts-header-example", + "version": "1.0.0", + "devDependencies": { + "@tsconfig/strictest": "^2.0.6", + "@types/node": "^24.8.1", + "tsx": "^4.20.6", + "typescript": "^5.9.3", + "vitest": "^0.25.6" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@tsconfig/strictest": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/strictest/-/strictest-2.0.8.tgz", + "integrity": "sha512-XnQ7vNz5HRN0r88GYf1J9JJjqtZPiHt2woGJOo2dYqyHGGcd6OLGqSlBB6p1j9mpzja6Oe5BoPqWmeDx6X9rLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "4.3.20", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", + "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai-subset": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.6.tgz", + "integrity": "sha512-m8lERkkQj+uek18hXOZuec3W/fCRTrU4hrnXjH3qhHy96ytuPaPiWGgu7sJb7tZxZonO75vYAjCvpe/e4VUwRw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/chai": "<5.2.0" + } + }, + "node_modules/@types/node": { + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rollup": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.30.0.tgz", + "integrity": "sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz", + "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-1.1.1.tgz", + "integrity": "sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "4.5.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz", + "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/vitest": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.25.8.tgz", + "integrity": "sha512-X75TApG2wZTJn299E/TIYevr4E9/nBo1sUtZzn0Ci5oK8qnpZAZyhwg0qCeMSakGIWtc6oRwcQFyFfW14aOFWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^4.3.4", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "acorn": "^8.8.1", + "acorn-walk": "^8.2.0", + "chai": "^4.3.7", + "debug": "^4.3.4", + "local-pkg": "^0.4.2", + "source-map": "^0.6.1", + "strip-literal": "^1.0.0", + "tinybench": "^2.3.1", + "tinypool": "^0.3.0", + "tinyspy": "^1.0.2", + "vite": "^3.0.0 || ^4.0.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.16.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + } + } +} diff --git a/_examples/node-ts-header/package.json b/_examples/node-ts-header/package.json new file mode 100644 index 0000000..71163e4 --- /dev/null +++ b/_examples/node-ts-header/package.json @@ -0,0 +1,15 @@ +{ + "name": "node-ts-header-example", + "version": "1.0.0", + "type": "module", + "scripts": { + "test": "vitest run --reporter verbose" + }, + "devDependencies": { + "@tsconfig/strictest": "^2.0.6", + "@types/node": "^24.8.1", + "tsx": "^4.20.6", + "typescript": "^5.9.3", + "vitest": "^0.25.6" + } +} diff --git a/_examples/node-ts-header/server.ts b/_examples/node-ts-header/server.ts new file mode 100644 index 0000000..568c82d --- /dev/null +++ b/_examples/node-ts-header/server.ts @@ -0,0 +1,96 @@ +import http from 'node:http' +import { + ExampleServiceServer, + serveExampleServiceRpc, + UnauthorizedError, +} from './example.gen.js' + +const exampleService: ExampleServiceServer = { + async ping() { + return {} + }, + + async getUser(_ctx, { authToken, userID }) { + if (!authToken) { + throw UnauthorizedError.new({ cause: 'missing auth token' }) + } + return { + user: { id: userID, username: 'alice' }, + } + }, + + async createUser(_ctx, { authToken, username }) { + if (!authToken) { + throw UnauthorizedError.new({ cause: 'missing auth token' }) + } + return { + user: { id: 1, username }, + } + }, + + async deleteUser(_ctx, { authToken }) { + if (!authToken) { + throw UnauthorizedError.new({ cause: 'missing auth token' }) + } + return {} + }, +} + +const readBody = (req: http.IncomingMessage): Promise => + new Promise((resolve, reject) => { + let data = '' + req.on('data', (chunk: Buffer) => { data += chunk.toString('utf8') }) + req.on('end', () => resolve(data)) + req.on('error', reject) + }) + +const getHeaders = (req: http.IncomingMessage): Record => { + const headers: Record = {} + for (const [key, value] of Object.entries(req.headers)) { + if (typeof value === 'string') { + headers[key] = value + } + } + return headers +} + +export const startServer = (port: number): Promise => { + const server = http.createServer(async (req, res) => { + const url = req.url || '' + + if (req.method !== 'POST') { + res.writeHead(405).end() + return + } + + const raw = await readBody(req) + let body: any = {} + if (raw.length > 0) { + try { + body = JSON.parse(raw) + } catch { + res.writeHead(400, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ msg: 'invalid JSON' })) + return + } + } + + const reqHeaders = getHeaders(req) + const result = await serveExampleServiceRpc(exampleService, undefined, url, body, reqHeaders) + if (!result) { + res.writeHead(404).end() + return + } + + const payload = JSON.stringify(result.body ?? {}) + res.writeHead(result.status, { + ...result.headers, + 'Content-Length': Buffer.byteLength(payload), + }) + res.end(payload) + }) + + return new Promise((resolve) => { + server.listen(port, () => resolve(server)) + }) +} diff --git a/_examples/node-ts-header/tsconfig.json b/_examples/node-ts-header/tsconfig.json new file mode 100644 index 0000000..35907d4 --- /dev/null +++ b/_examples/node-ts-header/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@tsconfig/strictest/tsconfig.json", + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "resolveJsonModule": true, + "types": ["node"] + }, + "include": ["."] +} diff --git a/_examples/node-ts/server-fastify/server.gen.ts b/_examples/node-ts/server-fastify/server.gen.ts index ed04dba..dc305a3 100644 --- a/_examples/node-ts/server-fastify/server.gen.ts +++ b/_examples/node-ts/server-fastify/server.gen.ts @@ -1,7 +1,7 @@ /* eslint-disable */ // node-ts v1.0.0 83e44adec2381759b0925cfad2f5e25da97ad163 // -- -// Code generated by Webrpc-gen@v0.36.0 with ../../../gen-typescript generator. DO NOT EDIT. +// Code generated by Webrpc-gen@v0.25.0-3-g448298d with ../../../gen-typescript generator. DO NOT EDIT. // // webrpc-gen -schema=service.ridl -target=../../../gen-typescript -server -out=./server-fastify/server.gen.ts @@ -78,13 +78,13 @@ export interface GetUserResponse { // Server handler // -export const serveExampleRpc = async (service: ExampleServer, ctx: Context, urlPath: string, body: any) => { +export const serveExampleRpc = async (service: ExampleServer, ctx: Context, urlPath: string, body: any, reqHeaders?: Record) => { if (!urlPath.startsWith('/rpc/')) return null const parts = urlPath.split('/').filter(Boolean) if (parts.length !== 3 || parts[0] !== 'rpc' || parts[1] !== 'Example') return null const method = parts[2]! try { - const result = await dispatchExampleRequest(service, ctx, method, body) + const result = await dispatchExampleRequest(service, ctx, method, body, reqHeaders) return { method, status: 200, @@ -111,7 +111,7 @@ export const serveExampleRpc = async (service: ExampleServer, } } -const dispatchExampleRequest = async (service: ExampleServer, ctx: Context, method: string, body: any) => { +const dispatchExampleRequest = async (service: ExampleServer, ctx: Context, method: string, body: any, reqHeaders?: Record) => { let payload: any = body let result: any switch (method) { @@ -555,7 +555,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { export const WebrpcHeader = "Webrpc" -export const WebrpcHeaderValue = "webrpc@v0.36.0;gen-typescript@unknown;node-ts@v1.0.0" +export const WebrpcHeaderValue = "webrpc@v0.25.0-3-g448298d;gen-typescript@unknown;node-ts@v1.0.0" type WebrpcGenVersions = { WebrpcGenVersion: string; diff --git a/_examples/node-ts/server-hono/server.gen.ts b/_examples/node-ts/server-hono/server.gen.ts index af83320..bd81919 100644 --- a/_examples/node-ts/server-hono/server.gen.ts +++ b/_examples/node-ts/server-hono/server.gen.ts @@ -1,7 +1,7 @@ /* eslint-disable */ // node-ts v1.0.0 83e44adec2381759b0925cfad2f5e25da97ad163 // -- -// Code generated by Webrpc-gen@v0.36.0 with ../../../gen-typescript generator. DO NOT EDIT. +// Code generated by Webrpc-gen@v0.25.0-3-g448298d with ../../../gen-typescript generator. DO NOT EDIT. // // webrpc-gen -schema=service.ridl -target=../../../gen-typescript -server -out=./server-hono/server.gen.ts @@ -78,13 +78,13 @@ export interface GetUserResponse { // Server handler // -export const serveExampleRpc = async (service: ExampleServer, ctx: Context, urlPath: string, body: any) => { +export const serveExampleRpc = async (service: ExampleServer, ctx: Context, urlPath: string, body: any, reqHeaders?: Record) => { if (!urlPath.startsWith('/rpc/')) return null const parts = urlPath.split('/').filter(Boolean) if (parts.length !== 3 || parts[0] !== 'rpc' || parts[1] !== 'Example') return null const method = parts[2]! try { - const result = await dispatchExampleRequest(service, ctx, method, body) + const result = await dispatchExampleRequest(service, ctx, method, body, reqHeaders) return { method, status: 200, @@ -111,7 +111,7 @@ export const serveExampleRpc = async (service: ExampleServer, } } -const dispatchExampleRequest = async (service: ExampleServer, ctx: Context, method: string, body: any) => { +const dispatchExampleRequest = async (service: ExampleServer, ctx: Context, method: string, body: any, reqHeaders?: Record) => { let payload: any = body let result: any switch (method) { @@ -555,7 +555,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { export const WebrpcHeader = "Webrpc" -export const WebrpcHeaderValue = "webrpc@v0.36.0;gen-typescript@unknown;node-ts@v1.0.0" +export const WebrpcHeaderValue = "webrpc@v0.25.0-3-g448298d;gen-typescript@unknown;node-ts@v1.0.0" type WebrpcGenVersions = { WebrpcGenVersion: string; diff --git a/_examples/node-ts/server/server.gen.ts b/_examples/node-ts/server/server.gen.ts index 9d703ea..888281d 100644 --- a/_examples/node-ts/server/server.gen.ts +++ b/_examples/node-ts/server/server.gen.ts @@ -1,7 +1,7 @@ /* eslint-disable */ // node-ts v1.0.0 83e44adec2381759b0925cfad2f5e25da97ad163 // -- -// Code generated by Webrpc-gen@v0.36.0 with ../../../gen-typescript generator. DO NOT EDIT. +// Code generated by Webrpc-gen@v0.25.0-3-g448298d with ../../../gen-typescript generator. DO NOT EDIT. // // webrpc-gen -schema=service.ridl -target=../../../gen-typescript -server -out=./server/server.gen.ts @@ -78,13 +78,13 @@ export interface GetUserResponse { // Server handler // -export const serveExampleRpc = async (service: ExampleServer, ctx: Context, urlPath: string, body: any) => { +export const serveExampleRpc = async (service: ExampleServer, ctx: Context, urlPath: string, body: any, reqHeaders?: Record) => { if (!urlPath.startsWith('/rpc/')) return null const parts = urlPath.split('/').filter(Boolean) if (parts.length !== 3 || parts[0] !== 'rpc' || parts[1] !== 'Example') return null const method = parts[2]! try { - const result = await dispatchExampleRequest(service, ctx, method, body) + const result = await dispatchExampleRequest(service, ctx, method, body, reqHeaders) return { method, status: 200, @@ -111,7 +111,7 @@ export const serveExampleRpc = async (service: ExampleServer, } } -const dispatchExampleRequest = async (service: ExampleServer, ctx: Context, method: string, body: any) => { +const dispatchExampleRequest = async (service: ExampleServer, ctx: Context, method: string, body: any, reqHeaders?: Record) => { let payload: any = body let result: any switch (method) { @@ -555,7 +555,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { export const WebrpcHeader = "Webrpc" -export const WebrpcHeaderValue = "webrpc@v0.36.0;gen-typescript@unknown;node-ts@v1.0.0" +export const WebrpcHeaderValue = "webrpc@v0.25.0-3-g448298d;gen-typescript@unknown;node-ts@v1.0.0" type WebrpcGenVersions = { WebrpcGenVersion: string; diff --git a/_examples/node-ts/webapp/client.gen.ts b/_examples/node-ts/webapp/client.gen.ts index 2192397..2e512ee 100644 --- a/_examples/node-ts/webapp/client.gen.ts +++ b/_examples/node-ts/webapp/client.gen.ts @@ -1,7 +1,7 @@ /* eslint-disable */ // node-ts v1.0.0 83e44adec2381759b0925cfad2f5e25da97ad163 // -- -// Code generated by Webrpc-gen@v0.36.0 with ../../../gen-typescript generator. DO NOT EDIT. +// Code generated by Webrpc-gen@v0.25.0-3-g448298d with ../../../gen-typescript generator. DO NOT EDIT. // // webrpc-gen -schema=service.ridl -target=../../../gen-typescript -client -out=./webapp/client.gen.ts @@ -496,7 +496,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { export const WebrpcHeader = "Webrpc" -export const WebrpcHeaderValue = "webrpc@v0.36.0;gen-typescript@unknown;node-ts@v1.0.0" +export const WebrpcHeaderValue = "webrpc@v0.25.0-3-g448298d;gen-typescript@unknown;node-ts@v1.0.0" type WebrpcGenVersions = { WebrpcGenVersion: string; diff --git a/_examples/sse/webapp/client.gen.ts b/_examples/sse/webapp/client.gen.ts index 493f6b4..4d6a787 100644 --- a/_examples/sse/webapp/client.gen.ts +++ b/_examples/sse/webapp/client.gen.ts @@ -1,7 +1,7 @@ /* eslint-disable */ // webrpc-sse-chat v1.0.0 8b6b6e0df6c23832e31534594d32955c3383689b // -- -// Code generated by Webrpc-gen@v0.36.0 with ../../ generator. DO NOT EDIT. +// Code generated by Webrpc-gen@v0.25.0-3-g448298d with ../../ generator. DO NOT EDIT. // // webrpc-gen -schema=service.ridl -target=../../ -client -out=./webapp/client.gen.ts @@ -133,8 +133,7 @@ export class Chat implements ChatClient { }) } - const _fetch = () => this.fetch(this.url('SubscribeUsers'), - createHttpRequest('{}', options.headers, options.signal) + const _fetch = () => this.fetch(this.url('SubscribeUsers'),createHttpRequest('{}', options.headers, options.signal) ).then(async (res) => { await sseResponse(res, options, _fetch) }, (error) => { @@ -565,7 +564,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { export const WebrpcHeader = "Webrpc" -export const WebrpcHeaderValue = "webrpc@v0.36.0;@unknown;webrpc-sse-chat@v1.0.0" +export const WebrpcHeaderValue = "webrpc@v0.25.0-3-g448298d;@unknown;webrpc-sse-chat@v1.0.0" type WebrpcGenVersions = { WebrpcGenVersion: string; diff --git a/client.go.tmpl b/client.go.tmpl index 9f47834..6134028 100644 --- a/client.go.tmpl +++ b/client.go.tmpl @@ -24,7 +24,7 @@ export class {{$service.Name}} implements {{$service.Name}}Client { private url(name: string): string { return this.hostname + this.path + name } - + queryKey = { {{- range $i, $method := $service.Methods}} {{- $methodReqName := "" -}} @@ -46,7 +46,12 @@ export class {{$service.Name}} implements {{$service.Name}}Client { {{- $methodRespName := "" -}} {{- if $method.Succinct -}} {{- $methodReqName = (index $method.Inputs 0).Type -}} - {{- $methodRespName = (index $method.Outputs 0).Type -}} + {{- if gt (len $method.Outputs) 0 -}} + {{- $methodRespName = (index $method.Outputs 0).Type -}} + {{- if isCoreType (index $method.Outputs 0).Type -}} + {{- $methodRespName = get $typeMap (index $method.Outputs 0).Type -}} + {{- end -}} + {{- end -}} {{- else -}} {{- if $opts.compat -}} {{- $methodReqName = printf "%sArgs" $method.Name -}} @@ -56,7 +61,20 @@ export class {{$service.Name}} implements {{$service.Name}}Client { {{- $methodRespName = printf "%sResponse" $method.Name -}} {{- end -}} {{- end}} - {{firstLetterToLower .Name}} = ({{template "methodInputs" dict "Method" . "Opts" $opts "TypeMap" $typeMap}}): {{if $method.StreamOutput}}WebrpcStreamController{{else}}{{if $method.Succinct}}Promise<{{(index $method.Outputs 0).Type}}>{{else}}Promise<{{$method.Name}}{{if $opts.compat}}Return{{else}}Response{{end}}>{{end}}{{end}} => { + {{- /* Check for header/body inputs */ -}} + {{- $hasHeaderInputs := false -}} + {{- $hasBodyInputs := false -}} + {{- if $method.HeaderFields -}} + {{- $hasHeaderInputs = true -}} + {{- if $method.BodyFields -}} + {{- $hasBodyInputs = true -}} + {{- end -}} + {{- else -}} + {{- if $method.Inputs | len -}} + {{- $hasBodyInputs = true -}} + {{- end -}} + {{- end}} + {{firstLetterToLower .Name}} = ({{template "methodInputs" dict "Method" . "Opts" $opts "TypeMap" $typeMap}}): {{if $method.StreamOutput}}WebrpcStreamController{{else}}{{if and $method.Succinct (gt (len $method.Outputs) 0)}}Promise<{{template "type" dict "Type" (index $method.Outputs 0).Type "TypeMap" $typeMap}}>{{else if $method.Succinct}}Promise{{else}}Promise<{{$method.Name}}{{if $opts.compat}}Return{{else}}Response{{end}}>{{end}}{{end}} => { {{- if $method.StreamOutput }} const abortController = new AbortController() const abortSignal = abortController.signal @@ -66,9 +84,31 @@ export class {{$service.Name}} implements {{$service.Name}}Client { signal: options.signal, }) } + {{- if $hasHeaderInputs }} + + const _headers: Record = { ...((options.headers || {}) as Record) } + {{- range $_, $hf := $method.HeaderFields}} + if (req.{{$hf.Name}} != null{{if $hf.OmitEmpty}} && req.{{$hf.Name}} !== {{if and (isCoreType $hf.Type) (eq (get $typeMap $hf.Type) "string")}}""{{else if and (isCoreType $hf.Type) (eq (get $typeMap $hf.Type) "boolean")}}false{{else}}0{{end}}{{end}}) { + _headers["{{$hf.Name}}"] = String(req.{{$hf.Name}}) + } + {{- end}} + {{- end}} const _fetch = () => this.fetch(this.url('{{.Name}}'), - {{if .Inputs | len }}createHttpRequest(JsonEncode(req), options.headers, abortSignal){{- else}}createHttpRequest('{}', options.headers, options.signal){{end }} + {{- if .Inputs | len }} + {{- if $hasHeaderInputs }} + createHttpRequest(JsonEncode({{- if $hasBodyInputs }}(() => { const { + {{- $hi := 0 -}} + {{- range $_, $hf := $method.HeaderFields -}} + {{- if $hi}}, {{end -}} + {{$hf.Name}}: _h{{$hi}} + {{- $hi = add $hi 1 -}} + {{- end}}, ..._body} = req; return _body})() + {{- else}}{}{{- end}}), _headers, abortSignal) + {{- else }} + createHttpRequest(JsonEncode(req), options.headers, abortSignal) + {{- end }} + {{- else}}createHttpRequest('{}', options.headers, options.signal){{end }} ).then(async (res) => { await sseResponse(res, options, _fetch) }, (error) => { @@ -82,15 +122,36 @@ export class {{$service.Name}} implements {{$service.Name}}Client { } } {{- else }} + {{- if $hasHeaderInputs }} + const _headers: Record = { ...((headers || {}) as Record) } + {{- range $_, $hf := $method.HeaderFields}} + if (req.{{$hf.Name}} != null{{if $hf.OmitEmpty}} && req.{{$hf.Name}} !== {{if and (isCoreType $hf.Type) (eq (get $typeMap $hf.Type) "string")}}""{{else if and (isCoreType $hf.Type) (eq (get $typeMap $hf.Type) "boolean")}}false{{else}}0{{end}}{{end}}) { + _headers["{{$hf.Name}}"] = String(req.{{$hf.Name}}) + } + {{- end}} + {{- end}} return this.fetch( this.url('{{.Name}}'), {{- if .Inputs | len }} + {{- if $hasHeaderInputs }} + createHttpRequest(JsonEncode({{- if $hasBodyInputs }}(() => { const { + {{- $hi := 0 -}} + {{- range $_, $hf := $method.HeaderFields -}} + {{- if $hi}}, {{end -}} + {{$hf.Name}}: _h{{$hi}} + {{- $hi = add $hi 1 -}} + {{- end}}, ..._body} = req; return _body})() + {{- else}}{}{{- end}}), _headers, signal)).then((res) => { + {{- else }} createHttpRequest(JsonEncode(req), headers, signal)).then((res) => { + {{- end }} {{- else }} createHttpRequest('{}', headers, signal)).then((res) => { {{- end }} return buildResponse(res).then(_data => { + {{- if $methodRespName }} return JsonDecode<{{$methodRespName}}>(_data, '{{$methodRespName}}') + {{- end }} }) }, (error) => { throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error instanceof Error ? error.message : String(error)}` }) diff --git a/clientInterface.go.tmpl b/clientInterface.go.tmpl index a7d3499..6d36cb4 100644 --- a/clientInterface.go.tmpl +++ b/clientInterface.go.tmpl @@ -28,7 +28,7 @@ export interface {{$service.Name}}Client { * @deprecated {{ $deprecated.Value }} */ {{- end }} - {{firstLetterToLower $method.Name}}({{template "methodInputs" dict "Method" $method "TypeMap" $typeMap "Opts" $opts}}): {{if $method.StreamOutput}}WebrpcStreamController{{else}}{{if $method.Succinct}}Promise<{{(index $method.Outputs 0).Type}}>{{else}}Promise<{{$method.Name}}{{if $opts.compat}}Return{{else}}Response{{end}}>{{end}}{{end}} + {{firstLetterToLower $method.Name}}({{template "methodInputs" dict "Method" $method "TypeMap" $typeMap "Opts" $opts}}): {{if $method.StreamOutput}}WebrpcStreamController{{else}}{{if and $method.Succinct (gt (len $method.Outputs) 0)}}Promise<{{template "type" dict "Type" (index $method.Outputs 0).Type "TypeMap" $typeMap}}>{{else if $method.Succinct}}Promise{{else}}Promise<{{$method.Name}}{{if $opts.compat}}Return{{else}}Response{{end}}>{{end}}{{end}} {{- if lt (add $i 1) (len $service.Methods)}}{{"\n"}}{{end}} {{- end}} } diff --git a/server.go.tmpl b/server.go.tmpl index 6855709..7392d51 100644 --- a/server.go.tmpl +++ b/server.go.tmpl @@ -10,13 +10,13 @@ // Server handler // {{range $_, $service := $services }} -export const serve{{$service.Name}}Rpc = async (service: {{$service.Name}}Server, ctx: Context, urlPath: string, body: any) => { +export const serve{{$service.Name}}Rpc = async (service: {{$service.Name}}Server, ctx: Context, urlPath: string, body: any, reqHeaders?: Record) => { if (!urlPath.startsWith('{{$basepath}}')) return null const parts = urlPath.split('/').filter(Boolean) if (parts.length !== 3 || parts[0] !== 'rpc' || parts[1] !== '{{$service.Name}}') return null const method = parts[2]! try { - const result = await dispatch{{$service.Name}}Request(service, ctx, method, body) + const result = await dispatch{{$service.Name}}Request(service, ctx, method, body, reqHeaders) return { method, status: 200, @@ -43,7 +43,7 @@ export const serve{{$service.Name}}Rpc = async (service: {{$service.Nam } } -const dispatch{{$service.Name}}Request = async (service: {{$service.Name}}Server, ctx: Context, method: string, body: any) => { +const dispatch{{$service.Name}}Request = async (service: {{$service.Name}}Server, ctx: Context, method: string, body: any, reqHeaders?: Record) => { {{- if $usesBigInts}} const methodTypes = SERVICE_METHOD_TYPES['{{$service.Name}}'][method] if (!methodTypes) { @@ -57,7 +57,44 @@ const dispatch{{$service.Name}}Request = async (service: {{$service.Nam let result: any switch (method) { {{- range $_, $method := $service.Methods}} + {{- $hasHeaderInputs := false -}} + {{- if $method.HeaderFields -}} + {{- $hasHeaderInputs = true -}} + {{- end}} case '{{$method.Name}}': + {{- if $hasHeaderInputs }} + { + const args: any = { ...(payload || {}) } + {{- range $_, $hf := $method.HeaderFields }} + {{- if and (isCoreType $hf.Type) (eq (get $typeMap $hf.Type) "number") }} + { + const _v = reqHeaders?.["{{$hf.Name}}"] ?? reqHeaders?.["{{$hf.Name | lower}}"] + if (_v && _v !== "") { + const _n = Number(_v) + if (Number.isNaN(_n)) { + throw new WebrpcBadRequestError({ cause: "invalid header argument: {{$hf.Name}}" }) + } + args["{{$hf.Name}}"] = _n + } else { + args["{{$hf.Name}}"] = 0 + } + } + {{- else if and (isCoreType $hf.Type) (eq (get $typeMap $hf.Type) "boolean") }} + { + const _v = (reqHeaders?.["{{$hf.Name}}"] ?? reqHeaders?.["{{$hf.Name | lower}}"])?.toLowerCase() + if (_v === "true" || _v === "1") { + args["{{$hf.Name}}"] = true + } else { + args["{{$hf.Name}}"] = false + } + } + {{- else }} + args["{{$hf.Name}}"] = reqHeaders?.["{{$hf.Name}}"] ?? reqHeaders?.["{{$hf.Name | lower}}"] ?? "" + {{- end }} + {{- end }} + result = await service.{{firstLetterToLower $method.Name}}(ctx, args) + } + {{- else }} {{- range $_, $input := $method.Inputs }} {{- if $method.Succinct }} if (payload && !validateType(payload, "{{template "jsType" dict "Type" $input.Type "TypeMap" $typeMap}}")) { @@ -75,6 +112,7 @@ const dispatch{{$service.Name}}Request = async (service: {{$service.Nam {{- end}} {{- end}} result = await service.{{firstLetterToLower $method.Name}}(ctx, payload || {}) + {{- end }} break {{end}} default: diff --git a/serverInterface.go.tmpl b/serverInterface.go.tmpl index ba4c372..d046429 100644 --- a/serverInterface.go.tmpl +++ b/serverInterface.go.tmpl @@ -13,7 +13,7 @@ export interface {{$service.Name}}Server { {{- range $_, $method := $service.Methods}} {{- if $method.Succinct }} - {{firstLetterToLower $method.Name}}(ctx: Context, req: {{(index $method.Inputs 0).Type}}): Promise<{{(index $method.Outputs 0).Type}}> + {{firstLetterToLower $method.Name}}(ctx: Context, req: {{(index $method.Inputs 0).Type}}): Promise<{{if gt (len $method.Outputs) 0}}{{template "type" dict "Type" (index $method.Outputs 0).Type "TypeMap" $typeMap}}{{else}}void{{end}}> {{- else}} {{- if $opts.compat }} {{firstLetterToLower $method.Name}}(ctx: Context, req: {{$method.Name}}Args): Promise<{{$method.Name}}Return>