@hautechai/sdk 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,40 +1,40 @@
1
1
  name: Publish Package to npmjs
2
2
  on:
3
- release:
4
- types: [published]
3
+ release:
4
+ types: [published]
5
5
  jobs:
6
- build-and-publish:
7
- runs-on: ubuntu-latest
8
- permissions:
9
- contents: read
10
- id-token: write
11
- environment: main
12
- steps:
13
- - name: Checkout
14
- uses: actions/checkout@v4
6
+ build-and-publish:
7
+ runs-on: ubuntu-latest
8
+ permissions:
9
+ contents: read
10
+ id-token: write
11
+ environment: main
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v4
15
15
 
16
- - name: Install pnpm
17
- uses: pnpm/action-setup@v4
18
- with:
19
- version: 9
16
+ - name: Install pnpm
17
+ uses: pnpm/action-setup@v4
18
+ with:
19
+ version: 9
20
20
 
21
- - name: Use Node LTS
22
- uses: actions/setup-node@v4
23
- with:
24
- node-version: '20.x'
25
- registry-url: 'https://registry.npmjs.org'
26
- cache: pnpm
21
+ - name: Use Node LTS
22
+ uses: actions/setup-node@v4
23
+ with:
24
+ node-version: '20.x'
25
+ registry-url: 'https://registry.npmjs.org'
26
+ cache: pnpm
27
27
 
28
- - name: Install dependencies
29
- run: pnpm install --frozen-lockfile
28
+ - name: Install dependencies
29
+ run: pnpm install --frozen-lockfile
30
30
 
31
- - name: Bump version
32
- run: pnpm version from-git --no-commit-hooks --no-git-tag-version --allow-same-version
31
+ - name: Bump version
32
+ run: pnpm version from-git --no-commit-hooks --no-git-tag-version --allow-same-version
33
33
 
34
- - name: Build the package
35
- run: pnpm build
34
+ - name: Build the package
35
+ run: pnpm build
36
36
 
37
- - name: Publish the package
38
- run: pnpm publish --no-git-checks --access public
39
- env:
40
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
37
+ - name: Publish the package
38
+ run: pnpm publish --no-git-checks --access public
39
+ env:
40
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -2,38 +2,38 @@ name: Test
2
2
  on:
3
3
  push:
4
4
  branches:
5
- - 'main'
5
+ - 'main'
6
6
  jobs:
7
- test:
8
- runs-on: ubuntu-latest
9
- permissions:
10
- contents: read
11
- id-token: write
12
- environment: main
13
- steps:
14
- - name: Checkout
15
- uses: actions/checkout@v4
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ permissions:
10
+ contents: read
11
+ id-token: write
12
+ environment: main
13
+ steps:
14
+ - name: Checkout
15
+ uses: actions/checkout@v4
16
16
 
17
- - name: Install pnpm
18
- uses: pnpm/action-setup@v4
19
- with:
20
- version: 9
17
+ - name: Install pnpm
18
+ uses: pnpm/action-setup@v4
19
+ with:
20
+ version: 9
21
21
 
22
- - name: Use Node LTS
23
- uses: actions/setup-node@v4
24
- with:
25
- node-version: '20.x'
26
- registry-url: 'https://registry.npmjs.org'
27
- cache: pnpm
22
+ - name: Use Node LTS
23
+ uses: actions/setup-node@v4
24
+ with:
25
+ node-version: '20.x'
26
+ registry-url: 'https://registry.npmjs.org'
27
+ cache: pnpm
28
28
 
29
- - name: Install dependencies
30
- run: pnpm install --frozen-lockfile
29
+ - name: Install dependencies
30
+ run: pnpm install --frozen-lockfile
31
31
 
32
- - name: Test
33
- run: pnpm test
34
- env:
35
- API_CORE_URL: https://api.dev.hautech.ai
36
- APP_ID: ${{ secrets.APP_ID }}
37
- APP_KEY_ID: ${{ secrets.APP_KEY_ID }}
38
- APP_KEY_SECRET: ${{ secrets.APP_KEY_SECRET }}
39
- SDK_TOKEN: ${{ secrets.SDK_TOKEN }}
32
+ - name: Test
33
+ run: pnpm test
34
+ env:
35
+ API_CORE_URL: https://api.dev.hautech.ai
36
+ APP_ID: ${{ secrets.APP_ID }}
37
+ APP_KEY_ID: ${{ secrets.APP_KEY_ID }}
38
+ APP_KEY_SECRET: ${{ secrets.APP_KEY_SECRET }}
39
+ SDK_TOKEN: ${{ secrets.SDK_TOKEN }}
@@ -0,0 +1 @@
1
+ src/autogenerated/**
package/dist/sdk/api.d.ts CHANGED
@@ -2,6 +2,8 @@ import { AxiosPromise } from 'axios';
2
2
  import { BaseAPI } from '../autogenerated/base';
3
3
  import { SDKOptions } from '../types';
4
4
  import { Configuration } from '../autogenerated';
5
+ export declare const getBaseUrl: (options: SDKOptions) => string;
6
+ export declare const getAPI: <T>(API: new (configuration?: Configuration, basePath?: string, axios?: any) => T, options: SDKOptions) => Promise<T>;
5
7
  export declare const useAutogeneratedAPI: <T extends BaseAPI>(baseProps: {
6
8
  API: new (configuration?: Configuration, basePath?: string, axios?: any) => T;
7
9
  options: SDKOptions;
package/dist/sdk/api.js CHANGED
@@ -1,15 +1,15 @@
1
- const getBaseUrl = (options) => options.endpoint ?? 'https://api.hautech.ai';
2
- export const useAutogeneratedAPI = (baseProps) => {
3
- const getAPI = async () => {
4
- const config = {
5
- accessToken: await baseProps.options.authToken(),
6
- basePath: getBaseUrl(baseProps.options),
7
- isJsonMime: (mime) => true,
8
- };
9
- return new baseProps.API(config);
1
+ export const getBaseUrl = (options) => options.endpoint ?? 'https://api.hautech.ai';
2
+ export const getAPI = async (API, options) => {
3
+ const config = {
4
+ accessToken: await options.authToken(),
5
+ basePath: getBaseUrl(options),
6
+ isJsonMime: (mime) => true,
10
7
  };
8
+ return new API(config);
9
+ };
10
+ export const useAutogeneratedAPI = (baseProps) => {
11
11
  const call = async (props) => {
12
- const api = await getAPI();
12
+ const api = await getAPI(baseProps.API, baseProps.options);
13
13
  try {
14
14
  const response = await props.run(api);
15
15
  if (props.returnUndefinedOn404 && response.status === 404)
@@ -0,0 +1,3 @@
1
+ export declare class HautechException extends Error {
2
+ constructor(message: string);
3
+ }
@@ -0,0 +1,6 @@
1
+ export class HautechException extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'HautechException';
5
+ }
6
+ }
@@ -202,7 +202,7 @@ export declare const createSDK: (options: SDKOptions) => {
202
202
  }) => Promise<void>;
203
203
  wait: <T extends import("../types").OperationEntity | {
204
204
  id: string;
205
- }>(props: T) => Promise<(T extends import("../types").OperationEntity ? T : import("../types").OperationEntity) & ({
205
+ }>(props: T, timeoutMs?: number) => Promise<((T extends import("../types").OperationEntity ? T : import("../types").OperationEntity) & ({
206
206
  status: "failed";
207
207
  output: null;
208
208
  } | {
@@ -211,7 +211,7 @@ export declare const createSDK: (options: SDKOptions) => {
211
211
  } | {
212
212
  status: "finished";
213
213
  output: NonNullable<(T extends import("../types").OperationEntity ? T : import("../types").OperationEntity)["output"]>;
214
- })>;
214
+ })) | null>;
215
215
  };
216
216
  pipelines: {
217
217
  constructTemplate: (consructPipeline: (pipeline: import("@hautechai/pipelines").Pipeline<{
@@ -509,5 +509,6 @@ export declare const createSDK: (options: SDKOptions) => {
509
509
  utils: {
510
510
  seed: () => number;
511
511
  };
512
+ close: () => void;
512
513
  };
513
514
  export type SDK = ReturnType<typeof createSDK>;
package/dist/sdk/index.js CHANGED
@@ -10,6 +10,9 @@ import pipelines from './pipelines';
10
10
  import stacks from './stacks';
11
11
  import storage from './storage';
12
12
  import utils from './utils';
13
+ import { OperationsListener } from './listeners';
14
+ import { getAPI, getBaseUrl } from './api';
15
+ import { OperationsApi } from '../autogenerated';
13
16
  export const createSDK = (options) => {
14
17
  let token = undefined;
15
18
  const authToken = async () => {
@@ -23,6 +26,17 @@ export const createSDK = (options) => {
23
26
  return token;
24
27
  };
25
28
  const optionsWithTokenRefresher = { ...options, authToken };
29
+ const operationsListener = new OperationsListener({
30
+ ws: options.useWebsocket
31
+ ? {
32
+ endpoint: getBaseUrl(options),
33
+ token: authToken,
34
+ }
35
+ : null,
36
+ // TODO: Refactor the API initialization
37
+ operations: () => getAPI(OperationsApi, optionsWithTokenRefresher),
38
+ });
39
+ operationsListener.subscribe();
26
40
  return {
27
41
  access: access(optionsWithTokenRefresher),
28
42
  accounts: accounts(optionsWithTokenRefresher),
@@ -30,10 +44,13 @@ export const createSDK = (options) => {
30
44
  collections: collections(optionsWithTokenRefresher),
31
45
  groups: groups(optionsWithTokenRefresher),
32
46
  images: images(optionsWithTokenRefresher),
33
- operations: operations(optionsWithTokenRefresher),
47
+ operations: operations(optionsWithTokenRefresher, operationsListener),
34
48
  pipelines: pipelines(optionsWithTokenRefresher),
35
49
  stacks: stacks(optionsWithTokenRefresher),
36
50
  storage: storage(optionsWithTokenRefresher),
37
51
  utils: utils(optionsWithTokenRefresher),
52
+ close: () => {
53
+ operationsListener.close();
54
+ },
38
55
  };
39
56
  };
@@ -0,0 +1,25 @@
1
+ import { OperationEntity, OperationsApi } from '../../autogenerated';
2
+ import { WebSocket } from 'ws';
3
+ export declare class OperationsListener {
4
+ useWebsocket: {
5
+ endpoint: string;
6
+ token: () => string | Promise<string>;
7
+ } | null;
8
+ ws: WebSocket | null;
9
+ operations: () => Promise<OperationsApi>;
10
+ constructor({ ws, operations, }: {
11
+ ws: {
12
+ endpoint: string;
13
+ token: () => string | Promise<string>;
14
+ } | null;
15
+ operations: () => Promise<OperationsApi>;
16
+ });
17
+ operationsStore: Record<string, OperationEntity>;
18
+ getOperation(id: string): Promise<OperationEntity | null>;
19
+ private updateOperation;
20
+ subscribe(): Promise<void>;
21
+ onOpen(): void;
22
+ onMessage(msg: string): void;
23
+ onClose(number: number, reason: Buffer): void;
24
+ close(): void;
25
+ }
@@ -0,0 +1,81 @@
1
+ import { WebSocket } from 'ws';
2
+ import { HautechException } from '../exceptions';
3
+ // This is pretty much a dirty solution until we need to rework this part.
4
+ export class OperationsListener {
5
+ constructor({ ws, operations, }) {
6
+ this.useWebsocket = null;
7
+ this.ws = null;
8
+ this.operationsStore = {};
9
+ if (ws) {
10
+ this.useWebsocket = {
11
+ endpoint: ws?.endpoint,
12
+ token: ws?.token,
13
+ };
14
+ }
15
+ this.operations = operations;
16
+ }
17
+ async getOperation(id) {
18
+ const isWsReady = this.ws?.readyState == WebSocket.OPEN;
19
+ if (!this.operationsStore[id] || !isWsReady) {
20
+ const api = await this.operations();
21
+ const operation = await api.operationsControllerGetOperationV1(id);
22
+ if (operation.status == 200)
23
+ this.updateOperation(id, operation.data);
24
+ }
25
+ return this.operationsStore[id] || null;
26
+ }
27
+ updateOperation(id, operation) {
28
+ const stored = this.operationsStore[id];
29
+ if (!stored || stored.updatedAt < operation.updatedAt) {
30
+ this.operationsStore[id] = operation;
31
+ }
32
+ }
33
+ async subscribe() {
34
+ if (!this.useWebsocket)
35
+ return;
36
+ try {
37
+ this.ws = new WebSocket(this.useWebsocket.endpoint, ['1', await this.useWebsocket.token()]);
38
+ this.ws.on('open', () => this.onOpen());
39
+ this.ws.on('message', (msg) => this.onMessage(msg));
40
+ this.ws.on('close', (number, reason) => this.onClose(number, reason));
41
+ }
42
+ catch (err) {
43
+ throw new HautechException(`SDK failed to open websocket: ${err}`);
44
+ }
45
+ }
46
+ onOpen() {
47
+ if (!this.ws)
48
+ throw new HautechException('Semantics error: this is a bug.');
49
+ this.ws.send(JSON.stringify({
50
+ event: 'subscribe',
51
+ data: {
52
+ channel: 'own_resources',
53
+ },
54
+ }));
55
+ }
56
+ onMessage(msg) {
57
+ if (!this.ws)
58
+ throw new HautechException('Semantics error: this is a bug.');
59
+ const { event, data } = JSON.parse(msg);
60
+ switch (event) {
61
+ case 'subscription:created':
62
+ break;
63
+ case 'operation:created':
64
+ case 'operation:updated':
65
+ this.updateOperation(data.id, data);
66
+ break;
67
+ }
68
+ }
69
+ onClose(number, reason) {
70
+ if (!this.ws)
71
+ throw new HautechException('Semantics error: this is a bug.');
72
+ // Reset dirty state.
73
+ this.operationsStore = {};
74
+ this.ws = null;
75
+ }
76
+ close() {
77
+ if (!this.ws)
78
+ return;
79
+ this.ws.close();
80
+ }
81
+ }
@@ -1,5 +1,6 @@
1
1
  import { CompositeV1Input, CompositeV1Response, CutV1Input, CutV1Response, GiseleVtonV1Input, GPTV1Input, GptV1Response, HauteLindaV1Response, HauteNaomiV1Response, ImagineKateV1Response, KateImagineV1Input, KateInpaintV1Input, LindaHauteV1Input, NaomiHauteV1Input, NegateImageV1Input, NegateImageV1Response, ObjectDetectionV1Input, ObjectDetectionV1Response, OperationEntity, OperationEntityStatusEnum, PoseEstimationV1Input, PoseEstimationV1Response, SegmentAnythingEmbeddingsV1Input, SegmentAnythingEmbeddingsV1Response, SegmentAnythingMaskV1Input, SegmentAnythingMaskV1Response, UpscaleV1Input, UpscaleV1Response, VtonGiseleV1Response } from '../../autogenerated';
2
2
  import { ListProps, ListResponse, SDKOptions } from '../../types';
3
+ import { OperationsListener } from '../listeners';
3
4
  type Waited<T extends OperationEntity> = T & ({
4
5
  status: typeof OperationEntityStatusEnum.Failed;
5
6
  output: null;
@@ -10,7 +11,7 @@ type Waited<T extends OperationEntity> = T & ({
10
11
  status: typeof OperationEntityStatusEnum.Finished;
11
12
  output: NonNullable<T['output']>;
12
13
  });
13
- declare const operations: (options: SDKOptions) => {
14
+ declare const operations: (options: SDKOptions, relevantOperations: OperationsListener) => {
14
15
  run: {
15
16
  haute: {
16
17
  linda: {
@@ -120,6 +121,6 @@ declare const operations: (options: SDKOptions) => {
120
121
  }) => Promise<void>;
121
122
  wait: <T extends OperationEntity | {
122
123
  id: string;
123
- }>(props: T) => Promise<Waited<T extends OperationEntity ? T : OperationEntity>>;
124
+ }>(props: T, timeoutMs?: number) => Promise<Waited<T extends OperationEntity ? T : OperationEntity> | null>;
124
125
  };
125
126
  export default operations;
@@ -1,7 +1,8 @@
1
1
  import { OperationsApi, } from '../../autogenerated';
2
2
  import { useAutogeneratedAPI } from '../api';
3
3
  import { transformToListResponse } from '../transformers';
4
- const operations = (options) => {
4
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5
+ const operations = (options, relevantOperations) => {
5
6
  const api = useAutogeneratedAPI({ API: OperationsApi, options });
6
7
  const createOperation = (callMethod) => (props) => api.call({ run: (methods) => callMethod(methods, props) });
7
8
  return {
@@ -72,20 +73,23 @@ const operations = (options) => {
72
73
  updateMetadata: async (props) => api.call({
73
74
  run: (methods) => methods.operationsControllerUpdateMetadataV1(props.id, { overwrite: props.metadata }),
74
75
  }),
75
- wait: async (props) => new Promise((resolve, reject) => {
76
- const initialDelay = 5000;
77
- const delay = 2000;
78
- let timeoutId;
79
- const poll = async () => {
80
- const operation = await api.call({
81
- run: (methods) => methods.operationsControllerGetOperationV1(props.id),
82
- });
83
- if (operation.status !== 'pending')
84
- return resolve(operation);
85
- timeoutId = setTimeout(poll, delay);
76
+ wait: async (props, timeoutMs) => {
77
+ const deadline = timeoutMs ? Date.now() + timeoutMs : undefined;
78
+ const delay = 1000;
79
+ const poll = async (id) => {
80
+ const operation = await relevantOperations.getOperation(id);
81
+ if (operation?.status !== 'pending')
82
+ return operation;
83
+ return null;
86
84
  };
87
- timeoutId = setTimeout(poll, initialDelay);
88
- }),
85
+ while (!deadline || Date.now() < deadline) {
86
+ const polled = await poll(props.id);
87
+ if (polled)
88
+ return polled;
89
+ await sleep(delay);
90
+ }
91
+ return null;
92
+ },
89
93
  };
90
94
  };
91
95
  export default operations;
@@ -31,24 +31,35 @@ const serializePermissions = (permissions) => {
31
31
  traverse(permissions);
32
32
  return result;
33
33
  };
34
- export const createTokenSigner = (options) => ({
35
- createAccountToken: async (props) => createToken({
36
- appKeyId: options.appKeyId,
37
- appKeySecret: options.appKeySecret,
38
- expiresInSeconds: props.expiresInSeconds,
39
- payload: {
40
- iss: options.appId,
41
- permissions: serializePermissions(props.permissions ?? {}),
42
- sub: props.accountId,
43
- },
44
- }),
45
- createRootToken: async (props) => createToken({
46
- appKeyId: options.appKeyId,
47
- appKeySecret: options.appKeySecret,
48
- expiresInSeconds: props.expiresInSeconds ?? 3600,
49
- payload: {
50
- iss: options.appId,
51
- permissions: ['*'],
52
- },
53
- }),
54
- });
34
+ export const createTokenSigner = (options) => {
35
+ if (!options.appId || options.appId.length === 0) {
36
+ throw new Error('appId is required');
37
+ }
38
+ if (!options.appKeyId || options.appKeyId.length === 0) {
39
+ throw new Error('appKeyId is required');
40
+ }
41
+ if (!options.appKeySecret || options.appKeySecret.length === 0) {
42
+ throw new Error('appKeySecret is required');
43
+ }
44
+ return {
45
+ createAccountToken: async (props) => createToken({
46
+ appKeyId: options.appKeyId,
47
+ appKeySecret: options.appKeySecret,
48
+ expiresInSeconds: props.expiresInSeconds,
49
+ payload: {
50
+ iss: options.appId,
51
+ permissions: serializePermissions(props.permissions ?? {}),
52
+ sub: props.accountId,
53
+ },
54
+ }),
55
+ createRootToken: async (props) => createToken({
56
+ appKeyId: options.appKeyId,
57
+ appKeySecret: options.appKeySecret,
58
+ expiresInSeconds: props.expiresInSeconds ?? 3600,
59
+ payload: {
60
+ iss: options.appId,
61
+ permissions: ['*'],
62
+ },
63
+ }),
64
+ };
65
+ };
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export interface SDKOptions {
2
2
  authToken: () => string | Promise<string>;
3
3
  endpoint?: string;
4
+ useWebsocket?: boolean;
4
5
  }
5
6
  export type ListProps = {
6
7
  cursor?: string;
package/openapitools.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
3
- "spaces": 2,
4
- "generator-cli": {
5
- "version": "7.10.0"
6
- }
2
+ "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
3
+ "spaces": 2,
4
+ "generator-cli": {
5
+ "version": "7.10.0"
6
+ }
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hautechai/sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "license": "MIT",
5
5
  "keywords": [],
6
6
  "repository": {
@@ -12,7 +12,8 @@
12
12
  "dependencies": {
13
13
  "@hautechai/pipelines": "0.1.0",
14
14
  "axios": "1.6.1",
15
- "jose": "5.9.6"
15
+ "jose": "5.9.6",
16
+ "ws": "^8.18.0"
16
17
  },
17
18
  "devDependencies": {
18
19
  "@jest/globals": "^29.7.0",
@@ -20,6 +21,7 @@
20
21
  "@types/jest": "29.5.12",
21
22
  "@types/jsonwebtoken": "9.0.7",
22
23
  "@types/node": "22.5.2",
24
+ "@types/ws": "^8.5.14",
23
25
  "dotenv": "16.4.7",
24
26
  "jest": "29.7.0",
25
27
  "jest-environment-jsdom": "29.7.0",