commit af7612c70642874a21aa115c888c58b225955767 Author: Juergen Kunz Date: Tue May 5 12:03:46 2026 +0000 Add API package diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4336e0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +node_modules/ +dist/ +dist_*/ +dist_ts/ +coverage/ +.nyc_output/ +.nogit/ +.playwright-mcp/ +*.log +.DS_Store +.env +.env.* +!.env.example diff --git a/package.json b/package.json new file mode 100644 index 0000000..1aaa3c4 --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "@smarthome.exchange/api", + "version": "0.1.0", + "private": false, + "description": "Typed API client for smarthome.exchange hubs.", + "exports": { + ".": "./dist_ts/index.js" + }, + "type": "module", + "author": "Task Venture Capital GmbH", + "license": "MIT", + "scripts": { + "test": "tstest test/ --verbose --logfile --timeout 60", + "build": "tsbuild tsfolders --allowimplicitany", + "buildDocs": "tsdoc" + }, + "dependencies": { + "@api.global/typedrequest": "^3.3.0", + "@api.global/typedrequest-interfaces": "^3.0.19", + "@api.global/typedsocket": "^4.1.2", + "@push.rocks/smartpromise": "^4.2.4", + "@smarthome.exchange/interfaces": "workspace:*" + }, + "devDependencies": { + "@git.zone/tsbuild": "^4.4.0", + "@git.zone/tsdoc": "^2.0.3", + "@git.zone/tsrun": "^2.0.3", + "@git.zone/tstest": "^3.6.3", + "@types/node": "^25.6.0" + }, + "files": [ + "ts/**/*", + "dist/**/*", + "dist_*/**/*", + "dist_ts/**/*", + "readme.md", + "changelog.md", + "license" + ], + "browserslist": [ + "last 1 chrome versions" + ], + "packageManager": "pnpm@10.28.2" +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..ee4cbe0 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# @smarthome.exchange/api + +Typed API client for connecting tools, CLIs, and apps to a smarthome.exchange hub. diff --git a/test/test.node.ts b/test/test.node.ts new file mode 100644 index 0000000..9e3f591 --- /dev/null +++ b/test/test.node.ts @@ -0,0 +1,9 @@ +import { expect, tap } from '@git.zone/tstest/tapbundle'; +import { SmarthomeExchangeApiClient } from '../ts/index.js'; + +tap.test('creates an API client', async () => { + const client = new SmarthomeExchangeApiClient({ hubUrl: 'http://localhost:8080/' }); + expect(client).toBeInstanceOf(SmarthomeExchangeApiClient); +}); + +export default tap.start(); diff --git a/ts/classes.smarthomeexchangeapiclient.ts b/ts/classes.smarthomeexchangeapiclient.ts new file mode 100644 index 0000000..8b07801 --- /dev/null +++ b/ts/classes.smarthomeexchangeapiclient.ts @@ -0,0 +1,81 @@ +import * as plugins from './plugins.js'; + +export interface IShxApiClientOptions { + hubUrl?: string; + useSocket?: boolean; +} + +export class SmarthomeExchangeApiClient { + private hubUrl: string; + public typedrouter = new plugins.typedrequest.TypedRouter(); + public typedsocketClient?: plugins.typedsocket.TypedSocket; + + constructor(optionsArg: IShxApiClientOptions = {}) { + this.hubUrl = (optionsArg.hubUrl || 'http://localhost:8080').replace(/\/$/, ''); + } + + private get httpEndpoint() { + return `${this.hubUrl}/typedrequest`; + } + + private createWsRequest(operationArg: string) { + return this.typedsocketClient?.createTypedRequest(operationArg); + } + + private createHttpRequest(operationArg: string) { + return new plugins.typedrequest.TypedRequest(this.httpEndpoint, operationArg); + } + + private async fire( + operationArg: string, + payloadArg: T['request'] + ): Promise { + const wsRequest = this.createWsRequest(operationArg); + if (wsRequest) { + return wsRequest.fire(payloadArg); + } + return this.createHttpRequest(operationArg).fire(payloadArg); + } + + public async start() { + this.typedsocketClient = await plugins.typedsocket.TypedSocket.createClient( + this.typedrouter, + this.hubUrl + ); + } + + public async stop() { + await this.typedsocketClient?.stop(); + this.typedsocketClient = undefined; + } + + public async listDevices(requestArg: plugins.shxInterfaces.request.IReq_ListDevices['request'] = {}) { + return this.fire('listDevices', requestArg); + } + + public async listAgents() { + return this.fire('listAgents', {}); + } + + public async listTools(requestArg: plugins.shxInterfaces.request.IReq_ListTools['request'] = {}) { + return this.fire('listTools', requestArg); + } + + public async getHomeSnapshot() { + return this.fire('getHomeSnapshot', {}); + } + + public async executeToolPlan(planArg: plugins.shxInterfaces.data.IToolPlan) { + return this.fire('executeToolPlan', { + plan: planArg, + }); + } + + public async listApprovals(requestArg: plugins.shxInterfaces.request.IReq_ListApprovals['request'] = {}) { + return this.fire('listApprovals', requestArg); + } + + public async submitApproval(requestArg: plugins.shxInterfaces.request.IReq_SubmitApproval['request']) { + return this.fire('submitApproval', requestArg); + } +} diff --git a/ts/index.ts b/ts/index.ts new file mode 100644 index 0000000..f84eb04 --- /dev/null +++ b/ts/index.ts @@ -0,0 +1 @@ +export * from './classes.smarthomeexchangeapiclient.js'; diff --git a/ts/plugins.ts b/ts/plugins.ts new file mode 100644 index 0000000..ddcf0a0 --- /dev/null +++ b/ts/plugins.ts @@ -0,0 +1,16 @@ +// Project scope +import * as shxInterfaces from '@smarthome.exchange/interfaces'; + +export { shxInterfaces }; + +// @api.global scope +import * as typedrequest from '@api.global/typedrequest'; +import * as typedRequestInterfaces from '@api.global/typedrequest-interfaces'; +import * as typedsocket from '@api.global/typedsocket'; + +export { typedrequest, typedRequestInterfaces, typedsocket }; + +// @push.rocks scope +import * as smartpromise from '@push.rocks/smartpromise'; + +export { smartpromise }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7862634 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "verbatimModuleSyntax": true, + "types": ["node"] + }, + "exclude": [ + "dist_*/**/*.d.ts" + ] +}