JavaScript and TypeScript
helixor-sdk-gen 0.1.0 generates typed TypeScript clients from OpenAPI contracts. The package compiles to JavaScript for Node and browser consumers; there is no separate JavaScript emitter. For in-process decision execution, an embedded TypeScript SDK remains planned.
Generated OpenAPI client#
Select typescript in the SDK configuration. The generated package includes typed models and operations, configurable authentication, request and response validation, retry and timeout policy, and language-neutral operation contracts. Build output is ordinary JavaScript with TypeScript declarations.
package_name: example-sdk package_version: 0.1.0 languages: [typescript] base_url: https://api.example.test
Generation support does not mean a package has already been published to npm. Use the release manifest for the API you are integrating with to determine package availability. See the language capability matrix.
Decision Runtime 0.3.1#
An embedded TypeScript SDK generated from the pack manifest is Planned. Until it ships, TypeScript and Node callers use the local service:
- Start the service
Run
helixor-pack servefor a compiled pack (see CLI), orstart_serverfrom Python for the built-in example pack. It binds loopback by default. If callers on other hosts need it, give it a bearer token (HELIXOR_SERVICE_TOKEN) and sendAuthorization: Bearerfrom your client. - Call it with
fetchNode 18 and later include
fetch, so you need no dependencies.
Types#
These interfaces mirror the JSON the service returns. See the Python reference for what each field means.
export interface RegulatoryTrigger {
rule_id: string;
law: string;
severity: "FATAL" | "WARNING";
matched_items: string[]; // raw sensitive values: never log
}
export interface CounterfactualRemedy {
clean_text: string;
redactions_count: number;
redacted_categories: string[];
}
export interface DecisionResult {
pack_id: string;
action: string;
invariants_passed: boolean;
reason: string;
triggers: RegulatoryTrigger[];
remedy: CounterfactualRemedy;
latency_us: number;
tokens_spent: number;
egress_bytes: number;
receipt_hash: string;
license_status: string | null;
active_key_id: string | null;
active_epoch: number | null;
edition: string;
upgrade_notice: string | null;
}
export interface StreamChunkResult {
chunk_index: number;
text: string;
is_final: boolean;
redacted: boolean;
blocked: boolean;
action: string;
reason: string | null;
triggers: RegulatoryTrigger[];
}
Evaluate a payload#
import type { DecisionResult } from "./helixor-types";
const BASE_URL = process.env.HELIXOR_SERVICE_URL ?? "http://127.0.0.1:18734";
export async function evaluate(text: string): Promise<DecisionResult> {
const res = await fetch(`${BASE_URL}/v1/evaluate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
});
if (!res.ok) {
throw new Error(`evaluate failed: HTTP ${res.status} ${await res.text()}`);
}
return (await res.json()) as DecisionResult;
}
const result = await evaluate("Customer card 4111-1111-1111-1111, contact jane@example.com");
const mustBlock = result.triggers.some((t) => t.severity === "FATAL");
if (mustBlock) {
console.log("blocked", result.action, result.receipt_hash);
} else {
console.log("forward", result.remedy.clean_text);
}
HELIXOR_SERVICE_URL is a name used by this example, not a variable the runtime reads. Fail closed: if the service is unreachable or returns an error, do not forward the payload.
Read an SSE stream#
POST /v1/evaluate/stream takes the whole text and splits it on the server, so it suits testing more than live model output. For live tokens, keep the /v1/ws/decision WebSocket open and send stream_chunk messages. Both are described in the HTTP API reference.
import type { StreamChunkResult } from "./helixor-types";
const res = await fetch(`${BASE_URL}/v1/evaluate/stream`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: "Call (415) 555-0100 today.", chunk_size: 8 }),
});
const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
let buffer = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
let sep;
while ((sep = buffer.indexOf("\n\n")) >= 0) {
const line = buffer.slice(0, sep).trim();
buffer = buffer.slice(sep + 2);
if (!line.startsWith("data: ")) continue;
const chunk = JSON.parse(line.slice(6)) as StreamChunkResult;
if (chunk.blocked) throw new Error(`stream blocked: ${chunk.action}`);
process.stdout.write(chunk.text);
}
}