2026-04-16 16:44:26 +02:00
# NOMYO.js — Secure JavaScript Chat Client
2026-01-17 12:02:08 +01:00
**OpenAI-compatible secure chat client with end-to-end encryption for NOMYO Inference Endpoints**
2026-04-16 16:44:26 +02:00
- All prompts and responses are automatically encrypted and decrypted
- Hybrid encryption: AES-256-GCM payload + RSA-OAEP-SHA256 key exchange, 4096-bit keys
- Drop-in replacement for OpenAI's ChatCompletion API
- Works in both Node.js and browsers
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
## Quick Start
2026-01-17 12:02:08 +01:00
### Installation
```bash
npm install nomyo-js
```
### Basic Usage (Node.js)
```javascript
import { SecureChatCompletion } from 'nomyo-js';
const client = new SecureChatCompletion({
2026-04-16 16:44:26 +02:00
apiKey: process.env.NOMYO_API_KEY,
2026-01-17 12:02:08 +01:00
});
const response = await client.create({
model: 'Qwen/Qwen3-0.6B',
2026-04-16 16:44:26 +02:00
messages: [{ role: 'user', content: 'Hello!' }],
temperature: 0.7,
2026-01-17 12:02:08 +01:00
});
console.log(response.choices[0].message.content);
2026-04-16 16:44:26 +02:00
client.dispose();
2026-01-17 12:02:08 +01:00
```
### Basic Usage (Browser)
```html
2026-04-16 16:44:26 +02:00
< script type = "module" >
fix: correct package entry points and publish contents
Five packaging defects, all pre-existing:
1. dist/esm/index.js held ESM syntax while the package is not
"type": "module", so Node classified it as CommonJS. It failed
outright on Node 18 ("Unexpected token 'export'") and only worked on
Node >= 22 because Node re-parses after guessing the module type,
paying that cost on every import. Bundles now carry explicit
extensions: .mjs for ES output, .cjs/.js for CommonJS. The browser
build gained a real CommonJS output too — the exports map previously
pointed the browser "require" condition at an ES module.
The exports map now also leads with "types" and ends with a "default"
fallback for resolvers matching neither "node" nor "browser".
2. files: ["native"] published the local build directory: a 94.6 kB
Linux-x64 .node binary, a 148 kB object file and generated Makefiles.
node-gyp-build checks build/Release before prebuilds, so every
consumer on every platform would have found this machine's binary,
skipped compiling, and failed to load it. It fails safe (native/
index.js catches and returns null), but the addon could never work
for anyone else. Narrowed to the four source files.
3. binding.gyp resolves node-addon-api at build time, but nothing
declared it: it was a devDependency of the root, absent from
native/package.json. The build only succeeded here because a dev
install populates the root node_modules. Declared as a dependency of
the native package, where it is actually needed.
4. No clean step, so stale output shipped — the tarball carried both
dist/types/core/** and a dist/types/src/** tree left over from before
rootDir was set. build now runs clean first.
5. test:browser ran `karma start` with no karma.conf.js anywhere in the
repo, and tests/browser is an empty directory. Removed the script and
the karma devDependency rather than leave a script that cannot run.
Verified: CommonJS require and ESM import both resolve on Node 18.19.1
and 24.18.0; TypeScript resolves types under both bundler and node16;
npm pack now produces 35 files / 103.8 kB with no build artefacts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 12:39:09 +02:00
import { SecureChatCompletion } from 'https://unpkg.com/nomyo-js/dist/browser/index.mjs';
2026-04-16 16:44:26 +02:00
const client = new SecureChatCompletion({
baseUrl: 'https://api.nomyo.ai',
apiKey: 'your-api-key',
});
const response = await client.create({
model: 'Qwen/Qwen3-0.6B',
messages: [{ role: 'user', content: 'What is 2+2?' }],
});
console.log(response.choices[0].message.content);
< / script >
2026-01-17 12:02:08 +01:00
```
2026-04-16 16:44:26 +02:00
## Documentation
Full documentation is in the [`doc/` ](doc/ ) directory:
- [Getting Started ](doc/getting-started.md ) — walkthrough for new users
- [API Reference ](doc/api-reference.md ) — complete constructor options, methods, types, and error classes
- [Models ](doc/models.md ) — available models and selection guide
- [Security Guide ](doc/security-guide.md ) — encryption, memory protection, key management, compliance
- [Rate Limits ](doc/rate-limits.md ) — limits, automatic retry behaviour, batch throttling
- [Examples ](doc/examples.md ) — 12+ code examples for common scenarios
- [Troubleshooting ](doc/troubleshooting.md ) — error reference and debugging tips
## Security Features
2026-01-17 12:02:08 +01:00
### Hybrid Encryption
2026-04-16 16:44:26 +02:00
- **Payload encryption**: AES-256-GCM (authenticated encryption)
- **Key exchange**: RSA-OAEP-SHA256
2026-01-17 12:02:08 +01:00
- **Key size**: 4096-bit RSA keys
2026-04-16 16:44:26 +02:00
- **Scope**: All communication is end-to-end encrypted
2026-01-17 12:02:08 +01:00
### Key Management
2026-07-19 11:19:03 +02:00
- **Ephemeral by default**: A fresh key pair is generated in memory on first use and never written to disk. Set `keyDir` to persist and reuse keys across runs (Node.js only); existing keys in that directory are reloaded automatically.
2026-04-16 16:44:26 +02:00
- **Password protection**: Optional AES-encrypted private key files (minimum 8 characters).
- **Secure permissions**: Private key files saved at `0600` (owner-only).
- **Auto-rotation**: Keys rotate every 24 hours by default (configurable via `keyRotationInterval` ).
- **Explicit lifecycle**: Call `dispose()` to zero in-memory key material and stop the rotation timer.
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
### Secure Memory
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
The library wraps all intermediate sensitive buffers (AES keys, plaintext payload, decrypted bytes) in `SecureByteContext` , which zeroes them in a `finally` block immediately after use.
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
Pure JavaScript cannot lock pages to prevent OS swapping (`mlock` ). For environments where swap-file exposure is unacceptable, install the optional `nomyo-native` addon. Check the current protection level:
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
```javascript
import { getMemoryProtectionInfo } from 'nomyo-js';
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
const info = getMemoryProtectionInfo();
// Without addon: { method: 'zero-only', canLock: false }
// With addon: { method: 'mlock', canLock: true }
```
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
### Security Tiers
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
Pass `security_tier` per request to route inference to increasingly isolated hardware:
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
| Tier | Hardware | Use case |
|------|----------|----------|
| `"standard"` | GPU | General secure inference |
| `"high"` | CPU/GPU balanced | Sensitive business data |
| `"maximum"` | CPU only | HIPAA PHI, classified data |
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
```javascript
const response = await client.create({
model: 'Qwen/Qwen3-0.6B',
messages: [{ role: 'user', content: 'Patient record summary...' }],
security_tier: 'maximum',
});
```
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
## Usage Examples
### With API Key
2026-01-17 12:02:08 +01:00
```javascript
2026-04-16 16:44:26 +02:00
const client = new SecureChatCompletion({ apiKey: process.env.NOMYO_API_KEY });
2026-01-17 12:02:08 +01:00
```
2026-04-16 16:44:26 +02:00
### Error Handling
2026-01-17 12:02:08 +01:00
```javascript
2026-04-16 16:44:26 +02:00
import {
SecureChatCompletion,
AuthenticationError,
RateLimitError,
ForbiddenError,
} from 'nomyo-js';
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
try {
const response = await client.create({ model: 'Qwen/Qwen3-0.6B', messages: [...] });
} catch (err) {
if (err instanceof AuthenticationError) console.error('Check API key:', err.message);
else if (err instanceof RateLimitError) console.error('Rate limit hit:', err.message);
else if (err instanceof ForbiddenError) console.error('Model/tier mismatch:', err.message);
else throw err;
}
```
### Per-Request Router Override
Send a single request to a different router without changing the main client:
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
```javascript
2026-01-17 12:02:08 +01:00
const response = await client.create({
2026-04-16 16:44:26 +02:00
model: 'Qwen/Qwen3-0.6B',
messages: [{ role: 'user', content: 'Hello from secondary router' }],
base_url: 'https://secondary.nomyo.ai:12435', // temporary — main client unchanged
2026-01-17 12:02:08 +01:00
});
```
2026-04-16 16:44:26 +02:00
### Tool / Function Calling
2026-01-17 12:02:08 +01:00
```javascript
const response = await client.create({
2026-04-16 16:44:26 +02:00
model: 'Qwen/Qwen3-0.6B',
messages: [{ role: 'user', content: "What's the weather in Paris?" }],
2026-01-17 12:02:08 +01:00
tools: [
{
type: 'function',
function: {
2026-04-16 16:44:26 +02:00
name: 'get_weather',
description: 'Get weather information for a location',
2026-01-17 12:02:08 +01:00
parameters: {
type: 'object',
2026-04-16 16:44:26 +02:00
properties: { location: { type: 'string' } },
required: ['location'],
},
},
},
],
tool_choice: 'auto',
2026-01-17 12:02:08 +01:00
});
```
2026-04-16 16:44:26 +02:00
### Thinking Models
2026-01-17 12:02:08 +01:00
```javascript
const response = await client.create({
2026-04-16 16:44:26 +02:00
model: 'LiquidAI/LFM2.5-1.2B-Thinking',
messages: [{ role: 'user', content: 'Is 9.9 larger than 9.11?' }],
2026-01-17 12:02:08 +01:00
});
2026-04-16 16:44:26 +02:00
const { content, reasoning_content } = response.choices[0].message;
console.log('Reasoning:', reasoning_content);
console.log('Answer:', content);
2026-01-17 12:02:08 +01:00
```
2026-04-01 15:15:18 +02:00
### Resource Management
```javascript
2026-04-16 16:44:26 +02:00
const client = new SecureChatCompletion({ apiKey: process.env.NOMYO_API_KEY });
2026-04-01 15:15:18 +02:00
try {
const response = await client.create({ model: 'Qwen/Qwen3-0.6B', messages: [...] });
console.log(response.choices[0].message.content);
} finally {
2026-04-16 16:44:26 +02:00
client.dispose(); // zeros key material, stops rotation timer
2026-04-01 15:15:18 +02:00
}
```
2026-04-16 16:44:26 +02:00
### Local Development (HTTP)
2026-04-01 15:15:18 +02:00
```javascript
const client = new SecureChatCompletion({
2026-04-16 16:44:26 +02:00
baseUrl: 'http://localhost:12435',
allowHttp: true, // required — also prints a visible warning
2026-04-01 15:15:18 +02:00
});
```
2026-04-16 16:44:26 +02:00
## API Reference
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
### `SecureChatCompletion` — Constructor Options
2026-01-17 12:02:08 +01:00
```typescript
2026-04-16 16:44:26 +02:00
new SecureChatCompletion(config?: ChatCompletionConfig)
2026-01-17 12:02:08 +01:00
```
2026-04-16 16:44:26 +02:00
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `baseUrl` | `string` | `'https://api.nomyo.ai'` | NOMYO router URL. Must be HTTPS in production. |
| `allowHttp` | `boolean` | `false` | Allow HTTP connections. Local development only. |
| `apiKey` | `string` | `undefined` | Bearer token for `Authorization` header. |
| `secureMemory` | `boolean` | `true` | Zero sensitive buffers immediately after use. |
feat: align timeout, error types and docs with the Python SDK
Completes the parity work (step 4).
Request timeout now defaults to 900 s, matching Python, instead of 60 s.
Encrypted inference cannot stream, so an entire completion arrives in one
response; a long generation on a busy backend legitimately takes minutes
and was timing out here while succeeding in the Python client.
Error types now distinguish malformed data from integrity failures.
Python raises ValueError for a bad package, a non-200 or unparseable
/pki/public_key, and plaintext that will not parse, reserving
SecurityError for crypto failures. This port wrapped nearly all of it in
SecurityError — so a server sending malformed JSON was reported as an
authentication failure, pointing debugging in exactly the wrong
direction. Malformed data is now a plain Error (the JS equivalent of
ValueError), carried past the deliberately opaque catch-all by a symbol
marker rather than a new exported class. Genuine crypto failures still
report a single vague message so they cannot serve as a decryption
oracle.
Also adds the missing guard Python has: decrypting without a private key
now says so, instead of failing later and being reported as an integrity
failure.
doc/attestation.md ports the Python attestation guide to the JS API, and
documents the two deliberate divergences: no verify_ssl escape hatch, and
jose injection instead of a runtime dynamic import.
Version 0.1.0 -> 0.3.0 to match the Python client's feature level, now
that the two are at parity.
Not ported: Python's warning when secure_memory=True but the SecureMemory
module is unavailable. There is no JS equivalent — zeroing is always
available, and the weaker case (mlock unavailable) is already reported
honestly by getProtectionInfo().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 12:17:52 +02:00
| `timeout` | `number` | `900000` | Request timeout in milliseconds (15 min, matching the Python SDK). |
2026-04-16 16:44:26 +02:00
| `debug` | `boolean` | `false` | Print verbose logging to the console. |
2026-07-19 11:19:03 +02:00
| `keyDir` | `string \| null` | `null` | Directory to load/save RSA keys on startup. Omit for ephemeral in-memory keys that are never written to disk. Node.js only. |
2026-04-16 16:44:26 +02:00
| `keyRotationInterval` | `number` | `86400000` | Auto-rotate keys every N ms. `0` disables rotation. |
2026-07-19 11:19:03 +02:00
| `keyRotationDir` | `string` | `keyDir` | Directory for rotated key files. Rotated keys are only persisted when `keyDir` or `keyRotationDir` is set. Node.js only. |
2026-04-16 16:44:26 +02:00
| `keyRotationPassword` | `string` | `undefined` | Password for encrypted rotated key files. |
| `maxRetries` | `number` | `2` | Extra retry attempts on 429/5xx/network errors. Exponential backoff (1 s, 2 s, …). |
2026-01-17 12:02:08 +01:00
#### Methods
2026-04-16 16:44:26 +02:00
- `create(request): Promise<ChatCompletionResponse>` — send an encrypted chat completion
- `acreate(request): Promise<ChatCompletionResponse>` — alias for `create()`
- `dispose(): void` — zero key material and stop the rotation timer
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
#### `create()` Request Fields
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
All standard OpenAI fields (`model` , `messages` , `temperature` , `top_p` , `max_tokens` , `stop` , `n` , `tools` , `tool_choice` , `user` , `frequency_penalty` , `presence_penalty` , `logit_bias` ) plus:
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
| Field | Description |
|-------|-------------|
| `security_tier` | `"standard"` \| `"high"` \| `"maximum"` — hardware isolation level |
| `api_key` | Per-request API key override |
| `base_url` | Per-request router URL override — creates a temporary client, used once, then disposed |
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
### `SecureCompletionClient` — Constructor Options
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
Lower-level client. All options above apply, with these differences:
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `routerUrl` | `string` | `'https://api.nomyo.ai'` | Base URL (`baseUrl` is renamed here) |
| `keySize` | `2048 \| 4096` | `4096` | RSA modulus length |
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
#### Methods
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
- `generateKeys(options?)` — generate a new RSA key pair
- `loadKeys(privateKeyPath, publicKeyPath?, password?)` — load existing PEM files
- `fetchServerPublicKey()` — fetch the server's RSA public key
- `encryptPayload(payload)` — encrypt a request payload
- `decryptResponse(encrypted, payloadId)` — decrypt a response body
- `sendSecureRequest(payload, payloadId, apiKey?, securityTier?)` — full encrypt → POST → decrypt cycle
- `dispose()` — zero key material and stop rotation timer
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
### Secure Memory Public API
```typescript
import {
getMemoryProtectionInfo,
disableSecureMemory,
enableSecureMemory,
SecureByteContext,
} from 'nomyo-js';
2026-01-17 12:02:08 +01:00
```
2026-04-16 16:44:26 +02:00
| Export | Description |
|--------|-------------|
| `getMemoryProtectionInfo()` | Returns `{ method, canLock, isPlatformSecure, details? }` |
| `disableSecureMemory()` | Disable global secure-memory zeroing |
| `enableSecureMemory()` | Re-enable global secure-memory zeroing |
| `SecureByteContext` | Low-level buffer wrapper — zeros in `finally` block |
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
### Error Classes
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
```typescript
import {
AuthenticationError, InvalidRequestError, RateLimitError,
ForbiddenError, ServerError, ServiceUnavailableError,
APIConnectionError, SecurityError, DisposedError, APIError,
} from 'nomyo-js';
2026-01-17 12:02:08 +01:00
```
2026-04-16 16:44:26 +02:00
| Class | HTTP | Thrown when |
|-------|------|-------------|
| `AuthenticationError` | 401 | Invalid or missing API key |
| `InvalidRequestError` | 400 | Malformed request |
| `ForbiddenError` | 403 | Model not allowed for the security tier |
| `RateLimitError` | 429 | Rate limit exceeded (after all retries) |
| `ServerError` | 500 | Internal server error (after all retries) |
| `ServiceUnavailableError` | 503 | Backend unavailable (after all retries) |
| `APIError` | varies | Other HTTP errors |
| `APIConnectionError` | — | Network failure or timeout (after all retries) |
| `SecurityError` | — | HTTPS not used, header injection, or crypto failure |
| `DisposedError` | — | Method called after `dispose()` |
## Platform Support
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
### Node.js
2026-01-17 12:02:08 +01:00
2026-06-26 12:36:50 +00:00
- **Minimum**: Node.js 22.22.2+
- **Recommended**: Node.js 22 LTS or later
2026-04-16 16:44:26 +02:00
- **Key storage**: File system (`keyDir` directory, default `client_keys/` )
### Browsers
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
- **Supported**: Chrome 37+, Firefox 34+, Safari 11+, Edge 79+
- **Key storage**: In-memory only (not persisted)
- **Limitation**: File-based key operations (`keyDir` , `loadKeys` ) are not available
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
## Security Best Practices
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
- Always use HTTPS (`allowHttp` is `false` by default)
- Load API key from an environment variable, never hardcode it
- Use password-protected key files (`keyRotationPassword` )
- Store keys outside the project directory and outside version control
- Add `client_keys/` and `*.pem` to `.gitignore`
- Call `dispose()` when the client is no longer needed
- Use `security_tier: 'maximum'` for HIPAA PHI or classified data
- Consider the `nomyo-native` addon if swap-file exposure is unacceptable
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
## License
2026-01-17 12:02:08 +01:00
2026-04-16 16:44:26 +02:00
See LICENSE file.