Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@bluexlab/maos-ts

Package Overview
Dependencies
Maintainers
0
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bluexlab/maos-ts - npm Package Compare versions

Comparing version 0.0.2 to 0.0.3

39

dist/index.d.ts

@@ -0,4 +1,10 @@

import { ResultAsync } from 'neverthrow';
import { Level } from 'pino';
interface MaosConfig {
apiKey?: string;
coreUrl?: string;
logLevel?: Level;
invocationPollInterval?: number;
concurrentInvocationNum?: number;
}

@@ -8,2 +14,13 @@ interface MaosResponse {

}
interface InvocationMetaType extends Record<string, string> {
kind: string;
}
interface Invocation {
id: string;
meta: InvocationMetaType;
payload: Record<string, unknown>;
}
type HandlerInputType = Record<string, unknown>;
type HandlerResultType = ResultAsync<Record<string, unknown>, Error>;
type HandlerType = (input: HandlerInputType) => HandlerResultType;

@@ -13,7 +30,25 @@ declare class Maos {

private coreUrl;
private readonly minInvocationInterval;
private invocationPollInterval;
private concurrentInvocationNum;
private logLevel;
private handlers;
private shouldRun;
private logger;
private abortController;
private abortSignal;
constructor(config?: MaosConfig);
private fetchWithAuth;
getConfig(): Promise<MaosResponse>;
private postWithAuth;
getConfig(): ResultAsync<MaosResponse, Error>;
private getNextInvocation;
private respondToInvocation;
on(methodName: string, handler: HandlerType): Maos;
private getHandler;
private handleInvocation;
private worker;
shutdown(): void;
run(): Promise<void>;
}
export { Maos, type MaosConfig, type MaosResponse };
export { type HandlerInputType, type HandlerResultType, type HandlerType, type Invocation, type InvocationMetaType, Maos, type MaosConfig, type MaosResponse };
// src/maos.ts
import process from "process";
import process from "node:process";
import pino from "pino";
import { Result, ResultAsync as ResultAsync2, err, errAsync, ok, okAsync } from "neverthrow";
// src/lib/errorHandler.ts
function formatMaosError(error) {
return error instanceof Error ? error.message : String(error);
}
// src/lib/utils.ts
import { ResultAsync } from "neverthrow";
function isInvocation(obj) {
return "id" in obj && "payload" in obj;
}
function sleep(ms, signal) {
return ResultAsync.fromPromise(new Promise((resolve, reject) => {
let timeoutId = null;
const onAbort = () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
signal?.removeEventListener("abort", onAbort);
reject(new Error("Aborted"));
};
timeoutId = setTimeout(() => {
signal?.removeEventListener("abort", onAbort);
resolve();
}, ms);
if (signal) {
if (signal.aborted) {
clearTimeout(timeoutId);
reject(new Error("Aborted"));
} else {
signal.addEventListener("abort", onAbort);
}
}
}), (err2) => err2);
}
function joinUrl(base, ...parts) {
const url = new URL(base);
parts.forEach((part) => {
url.pathname = new URL(part, url).pathname;
});
return url.toString();
}
function jsonValueConverter(key, value) {
return typeof value === "bigint" ? value.toString() : value;
}
// src/maos.ts
var NotFoundError = class extends Error {
constructor() {
super("Not found");
this.name = "NotFoundError";
}
};
var Maos = class {
// configs
apiKey;
coreUrl;
minInvocationInterval = 500;
// ms
invocationPollInterval;
concurrentInvocationNum;
logLevel;
// internal state
handlers;
shouldRun = true;
logger;
abortController;
abortSignal;
constructor(config = {}) {
this.apiKey = config.apiKey || process.env.APIKEY || "";
this.coreUrl = config.coreUrl || process.env.CORE_URL || "";
this.logLevel = config.logLevel || process.env.LOG_LEVEL || "info";
this.invocationPollInterval = config.invocationPollInterval || Number.parseInt(process.env.INVOCATION_POLL_INTERVAL || "0") || 10 * 1e3;
this.concurrentInvocationNum = config.concurrentInvocationNum || Number.parseInt(process.env.CONCURRENT_INVOCATION_NUM || "1") || 1;
if (!this.apiKey) {

@@ -15,5 +85,9 @@ throw new Error("API key is required. Please provide it in the constructor or set the APIKEY environment variable.");

}
this.handlers = /* @__PURE__ */ new Map();
this.logger = pino({ level: this.logLevel });
this.abortController = new AbortController();
this.abortSignal = this.abortController.signal;
}
async fetchWithAuth(endpoint, options = {}) {
const url = `${this.coreUrl}${endpoint}`;
fetchWithAuth(endpoint, options = {}, allowAbort = false) {
const url = joinUrl(this.coreUrl, endpoint);
const headers = new Headers({

@@ -24,15 +98,115 @@ "Authorization": `Bearer ${this.apiKey}`,

});
const response = await fetch(url, { ...options, headers });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
const fetchPromise = fetch(url, { ...options, headers, signal: allowAbort ? this.abortSignal : void 0 });
if (!fetchPromise)
return errAsync(new NotFoundError());
return ResultAsync2.fromPromise(fetchPromise, (err2) => err2).andThen((res) => {
if (!res)
return err(new Error("No response"));
if (!res.ok) {
return err(
res.status === 404 ? new NotFoundError() : new Error(`HTTP error! status: ${res.status}, statusText: ${res.statusText}`)
);
}
return ok(res);
}).andThen((res) => ResultAsync2.fromPromise(res.text(), (err2) => err2).map((body) => ({ res, body }))).andThen(({ res, body }) => {
this.logger.info(`Response status: ${res.status}, ${res.statusText} body: ${body}`);
if (!body)
return ok({});
return ok(JSON.parse(body));
});
}
postWithAuth(endpoint, payload, allowAbort = false) {
return this.fetchWithAuth(endpoint, {
method: "POST",
body: JSON.stringify(payload, jsonValueConverter)
}, allowAbort);
}
getConfig() {
return this.fetchWithAuth("/v1/config").mapErr((err2) => err2 instanceof Error ? err2 : new Error(String(err2)));
}
getNextInvocation() {
return this.fetchWithAuth("/v1/invocations/next", void 0, true).mapErr((err2) => {
if (err2 instanceof NotFoundError)
return err2;
return new Error(`Failed to get next invocation: ${formatMaosError(err2)}`);
});
}
respondToInvocation(invokeId, result) {
return result.andThen((res) => this.postWithAuth(`/v1/invocations/${invokeId}/response`, { result: res })).orElse((err2) => this.postWithAuth(`/v1/invocations/${invokeId}/error`, {
errors: { message: formatMaosError(err2) }
})).mapErr((err2) => {
this.logger.error(`Failed to respond to invocation with id ${invokeId}: ${formatMaosError(err2)}`);
return err2;
});
}
// Register a handler for a method name
on(methodName, handler) {
if (this.handlers.has(methodName)) {
throw new Error(`Handler already registered with method name: ${methodName}`);
}
return await response.json();
this.handlers.set(methodName, handler);
return this;
}
async getConfig() {
try {
return await this.fetchWithAuth("/config");
} catch (error) {
throw new Error(`Failed to get config: ${error instanceof Error ? error.message : error}`);
getHandler(methodName) {
const handler = this.handlers.get(methodName);
if (!handler) {
return err(new Error(`Handler not found for method name: ${methodName}`));
}
return ok(handler);
}
handleInvocation() {
return this.getNextInvocation().andThen((response) => {
const id = response.id;
return ok(response).andThen((inv) => {
if (!isInvocation(inv))
return err(new Error(`Invocation response is not of valid type: ${JSON.stringify(inv, null, 2)}`));
const { meta, payload } = inv;
return ok({ meta, payload });
}).andThen(({ meta, payload }) => {
const kind = meta.kind;
return this.getHandler(kind).map((handler) => ({ payload, handler }));
}).andThen(
// trigger the handler within a throwable context
// even though the handler is expected not to throw, we can still catch any errors thrown
Result.fromThrowable(({ payload, handler }) => handler(payload))
).asyncAndThen(
(result) => result.andThen((res) => this.respondToInvocation(String(id), okAsync(res)))
).mapErr(async (err2) => {
this.respondToInvocation(String(id), errAsync(err2 instanceof Error ? err2 : new Error(String(err2))));
return err2;
});
});
}
async worker() {
let lastRun = Date.now();
while (this.shouldRun) {
await this.handleInvocation().mapErr((err2) => {
if (!(err2 instanceof NotFoundError)) {
this.logger.error(`Error during worker run: ${formatMaosError(err2)}`);
this.shouldRun = false;
}
});
const timeSinceLastRun = Date.now() - lastRun;
if (timeSinceLastRun < this.minInvocationInterval) {
await sleep(this.invocationPollInterval, this.abortSignal).mapErr((err2) => {
if (err2 instanceof Error && err2.message === "Aborted") {
this.logger.info("Worker sleep aborted");
this.shouldRun = false;
} else {
this.logger.error(`Error during worker sleep: ${formatMaosError(err2)}`);
}
});
}
lastRun = Date.now();
}
}
shutdown() {
this.abortController.abort();
this.shouldRun = false;
}
async run() {
process.on("SIGTERM", () => this.shutdown());
process.on("SIGINT", () => this.shutdown());
await Promise.all(Array.from({ length: this.concurrentInvocationNum }, () => this.worker()));
}
};

@@ -39,0 +213,0 @@ export {

28

package.json
{
"name": "@bluexlab/maos-ts",
"type": "module",
"version": "0.0.2",
"version": "0.0.3",
"packageManager": "pnpm@9.5.0",
"description": "TypeScript binding of MAOS",

@@ -27,3 +28,16 @@ "author": "Kevin Chung <kevin@bluextrade.com>",

},
"scripts": {
"lint": "eslint --cache .",
"lint:fix": "pnpm run lint --fix",
"build": "tsup",
"dev": "tsup --watch",
"test": "vitest",
"typecheck": "tsc --noEmit",
"format": "prettier --cache --write .",
"release": "bumpp && pnpm publish",
"prepublishOnly": "pnpm run build"
},
"dependencies": {
"neverthrow": "^7.0.0",
"pino": "^9.3.2",
"process": "^0.11.10"

@@ -41,13 +55,3 @@ },

"vitest": "^2.0.2"
},
"scripts": {
"lint": "eslint --cache .",
"lint:fix": "pnpm run lint --fix",
"build": "tsup",
"dev": "tsup --watch",
"test": "vitest",
"typecheck": "tsc --noEmit",
"format": "prettier --cache --write .",
"release": "bumpp && pnpm publish"
}
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc