Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ Thumbs.db
.fleet/
.zed/

# Rust
debug/
target/
**/*.rs.bk
*.pdb

# Javascript
test-results/
node_modules/
Expand Down
28 changes: 27 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,31 @@ and this project adheres to

### Removed

## [0.2.0] - 2025-10-18

### Added

- `ClientBuilder.fromConfig()` static method for creating builders from configuration objects
- `Client.fromConfig()` static method for creating clients from configuration objects
- `userAgent` configuration option and `NVISY_USER_AGENT` environment variable for custom user agent strings
- `ClientBuilder.withUserAgent()` method for setting custom user agent in builder pattern
- `DocumentsService` for document upload, management, and processing operations
- `IntegrationsService` for third-party service integrations
- `MembersService` for team member invitation and management

### Changed

- Environment variable names:
- `NVISY_API_KEY` → `NVISY_API_TOKEN`
- `NVISY_TIMEOUT` → `NVISY_MAX_TIMEOUT`
- Service names changed to plural (DocumentsService, IntegrationsService, MembersService)
- Client configuration validation now uses `ClientBuilder.fromConfig()` instead of internal validation method
- Client class is now readonly - configuration cannot be modified after creation

### Removed

- `Client.withConfig()` and other related methods (client is now readonly)

## [0.1.0] - 2025-10-15

### Added
Expand Down Expand Up @@ -45,5 +70,6 @@ and this project adheres to
- Network error handling for timeouts, DNS resolution, and connection issues
- Configuration validation with detailed error messages

[Unreleased]: https://github.com/nvisycom/sdk/compare/v0.1.0...HEAD
[Unreleased]: https://github.com/nvisycom/sdk/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/nvisycom/sdk/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/nvisycom/sdk/releases/tag/v0.1.0
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Makefile for Nvisy.com JavaScript & TypeScript SDK
# Makefile for Nvisy.com SDK for Node.JS

ifneq (,$(wildcard ./.env))
include .env
Expand Down
27 changes: 17 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# JavaScript & TypeScript SDK
# Nvisy.com SDK for Node.JS

[![npm version](https://img.shields.io/npm/v/@nvisy/sdk?color=000000&style=flat-square)](https://www.npmjs.com/package/@nvisy/sdk)
[![build](https://img.shields.io/github/actions/workflow/status/nvisycom/sdk/build.yml?branch=main&color=000000&style=flat-square)](https://github.com/nvisycom/sdk/actions/workflows/build.yml)
[![node](https://img.shields.io/badge/node-%3E%3D20.0.0-000000?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org/)
[![node](https://img.shields.io/badge/Node.JS-20.0+-000000?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org/)
[![typescript](https://img.shields.io/badge/TypeScript-5.9+-000000?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)

Official JavaScript & TypeScript SDK for the Nvisy document redaction platform.
Official Node.JS SDK for the Nvisy document redaction platform.

## Features

- Modern ES2022+ JavaScript with native private fields
- Modern ES2022+ JavaScript target
- Full TypeScript support with strict typing
- Flexible configuration with constructor or builder pattern
- Flexible configuration via a config object or builder pattern
- Built-in environment variable support
- Automatic retry logic with smart error handling
- Individual module exports for optimal bundling
Expand All @@ -36,6 +36,7 @@ const client = new Client({
baseUrl: "https://api.nvisy.com", // Optional: API endpoint (default shown)
timeout: 30000, // Optional: 1000-300000ms (default: 30000)
maxRetries: 3, // Optional: 0-5 attempts (default: 3)
userAgent: "MyApp/1.0.0", // Optional: custom user agent
headers: { // Optional: custom headers
"X-Custom-Header": "value",
},
Expand All @@ -54,6 +55,7 @@ const client = Client.builder()
.withBaseUrl("https://api.nvisy.com") // Optional: API endpoint (default shown)
.withTimeout(60000) // Optional: 1000-300000ms (default: 30000)
.withMaxRetries(5) // Optional: 0-5 attempts (default: 3)
.withUserAgent("MyApp/1.0.0") // Optional: custom user agent
.withHeader("X-Custom-Header", "value") // Optional: single custom header
.withHeaders({ "X-Another": "header" }) // Optional: multiple custom headers
.build();
Expand All @@ -79,15 +81,20 @@ Set these environment variables:

| Variable | Description | Required |
| ------------------- | -------------------------------- | -------- |
| `NVISY_API_KEY` | API key for authentication | Yes |
| `NVISY_API_TOKEN` | API key for authentication | Yes |
| `NVISY_BASE_URL` | Custom API endpoint URL | No |
| `NVISY_TIMEOUT` | Request timeout in milliseconds | No |
| `NVISY_MAX_TIMEOUT` | Request timeout in milliseconds | No |
| `NVISY_MAX_RETRIES` | Maximum number of retry attempts | No |
| `NVISY_USER_AGENT` | Custom user agent string | No |

## Requirements
## Services

- Node.js 20.0.0 or higher
- TypeScript 5.9.0 or higher (for development)
The SDK provides access to the following services:

- **Documents** - Document upload, management, and processing
- **Members** - Team member invitation and management
- **Integrations** - Third-party service integrations
- **Status** - API health and status monitoring

## Changelog

Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"files": {
"ignoreUnknown": false,
"includes": ["**", "!node_modules", "!dist", "!docs"]
"includes": ["**", "!node_modules", "!dist", "!docs", "!coverage"]
},
"formatter": {
"enabled": true,
Expand Down
24 changes: 14 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
{
"name": "@nvisy/sdk",
"version": "0.1.0",
"version": "0.2.0",
"description": "Official TypeScript SDK for Nvisy document redaction platform",
"type": "module",
"private": false,
"keywords": [
"nvisy",
"document",
"redaction",
"anonymization",
"privacy",
"ocr",
"sdk",
"api",
"typescript",
"client"
],
"author": "Nvisy <support@nvisy.com>",
"license": "MIT",
"homepage": "https://github.com/nvisycom/sdk#readme",
"bugs": {
"url": "https://github.com/nvisycom/sdk/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nvisycom/sdk.git"
},
"license": "MIT",
"author": "Nvisy <support@nvisy.com>",
"type": "module",
"bugs": {
"url": "https://github.com/nvisycom/sdk/issues"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
Expand All @@ -40,9 +47,6 @@
"import": "./dist/errors.js"
}
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"README.md",
Expand Down
84 changes: 73 additions & 11 deletions src/builder.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { ClientBuilder } from "./builder.js";
import { Client } from "./client.js";
import { ConfigError } from "./errors.js";
import { ClientBuilder } from "@/builder.js";
import { Client } from "@/client.js";
import { ConfigError } from "@/errors.js";

describe("ClientBuilder", () => {
const validApiKey = "test-api-key-123456";
Expand All @@ -12,10 +12,11 @@ describe("ClientBuilder", () => {
beforeAll(() => {
// Save original environment variables
originalEnv = {
NVISY_API_KEY: process.env.NVISY_API_KEY,
NVISY_API_TOKEN: process.env.NVISY_API_TOKEN,
NVISY_BASE_URL: process.env.NVISY_BASE_URL,
NVISY_TIMEOUT: process.env.NVISY_TIMEOUT,
NVISY_MAX_TIMEOUT: process.env.NVISY_MAX_TIMEOUT,
NVISY_MAX_RETRIES: process.env.NVISY_MAX_RETRIES,
NVISY_USER_AGENT: process.env.NVISY_USER_AGENT,
};
});

Expand All @@ -42,6 +43,46 @@ describe("ClientBuilder", () => {
ConfigError,
);
});

it("should create builder from config object", () => {
const config = {
apiKey: validApiKey,
baseUrl: "https://api.example.com",
timeout: 60000,
maxRetries: 5,
headers: { "X-Test": "header" },
};
const builder = ClientBuilder.fromConfig(config);
expect(builder).toBeInstanceOf(ClientBuilder);

const clientConfig = builder.getConfig();
expect(clientConfig.apiKey).toBe(validApiKey);
expect(clientConfig.baseUrl).toBe("https://api.example.com");
expect(clientConfig.timeout).toBe(60000);
expect(clientConfig.maxRetries).toBe(5);
expect(clientConfig.headers).toEqual({ "X-Test": "header" });
});

it("should create builder from config object with userAgent", () => {
const config = {
apiKey: validApiKey,
userAgent: "TestApp/1.0.0",
};
const builder = ClientBuilder.fromConfig(config);
expect(builder).toBeInstanceOf(ClientBuilder);

const clientConfig = builder.getConfig();
expect(clientConfig.userAgent).toBe("TestApp/1.0.0");
});

it("should validate API key in fromConfig method", () => {
expect(() => ClientBuilder.fromConfig({ apiKey: "short" })).toThrow(
ConfigError,
);
expect(() =>
ClientBuilder.fromConfig({ apiKey: "invalid chars!" }),
).toThrow(ConfigError);
});
});

describe("validation", () => {
Expand Down Expand Up @@ -97,15 +138,33 @@ describe("ClientBuilder", () => {

expect(client).toBeInstanceOf(Client);
});

it("should support withUserAgent method", () => {
const builder =
ClientBuilder.fromApiKey(validApiKey).withUserAgent(
"MyCustomApp/2.0.0",
);

const config = builder.getConfig();
expect(config.userAgent).toBe("MyCustomApp/2.0.0");
});

it("should validate userAgent string", () => {
const builder = ClientBuilder.fromApiKey(validApiKey);

expect(() => builder.withUserAgent("")).toThrow(ConfigError);
expect(() => builder.withUserAgent(" ")).toThrow(ConfigError);
});
});

describe("environment variable support", () => {
it("should handle missing environment variable gracefully", () => {
// Clear all relevant environment variables for this test
delete process.env.NVISY_API_KEY;
delete process.env.NVISY_API_TOKEN;
delete process.env.NVISY_BASE_URL;
delete process.env.NVISY_TIMEOUT;
delete process.env.NVISY_MAX_TIMEOUT;
delete process.env.NVISY_MAX_RETRIES;
delete process.env.NVISY_USER_AGENT;

expect(() => {
ClientBuilder.fromEnvironment();
Expand All @@ -114,10 +173,11 @@ describe("ClientBuilder", () => {

it("should create builder from environment variables", () => {
// Set up test environment variables
process.env.NVISY_API_KEY = "env-test-key-123456";
process.env.NVISY_API_TOKEN = "env-test-key-123456";
process.env.NVISY_BASE_URL = "https://api.test.nvisy.com";
process.env.NVISY_TIMEOUT = "15000";
process.env.NVISY_MAX_TIMEOUT = "15000";
process.env.NVISY_MAX_RETRIES = "5";
process.env.NVISY_USER_AGENT = "EnvApp/1.0.0";

const client = ClientBuilder.fromEnvironment().build();
const config = client.getConfig();
Expand All @@ -126,13 +186,15 @@ describe("ClientBuilder", () => {
expect(config.baseUrl).toBe("https://api.test.nvisy.com");
expect(config.timeout).toBe(15000);
expect(config.maxRetries).toBe(5);
expect(config.userAgent).toBe("EnvApp/1.0.0");
});

it("should support additional configuration after fromEnvironment", () => {
// Set minimal environment
process.env.NVISY_API_KEY = "env-key-123456";
process.env.NVISY_API_TOKEN = "env-key-123456";
delete process.env.NVISY_BASE_URL;
delete process.env.NVISY_TIMEOUT;
delete process.env.NVISY_MAX_TIMEOUT;
delete process.env.NVISY_USER_AGENT;

const client = ClientBuilder.fromEnvironment()
.withBaseUrl("https://override.example.com")
Expand Down
53 changes: 49 additions & 4 deletions src/builder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Client } from "./client.js";
import type { ClientConfig } from "./config.js";
import { loadConfigFromEnv } from "./config.js";
import { ConfigError } from "./errors.js";
import { Client } from "@/client.js";
import type { ClientConfig } from "@/config.js";
import { loadConfigFromEnv } from "@/config.js";
import { ConfigError } from "@/errors.js";

/**
* Reserved headers that cannot be overridden
Expand All @@ -21,6 +21,14 @@ export class ClientBuilder {
return new ClientBuilder().withApiKey(apiKey);
}

/**
* Set the API key for testing purposes
* @internal
*/
static fromTestingApiKey(): ClientBuilder {
return ClientBuilder.fromApiKey("test-key-123");
}

/**
* Create a ClientBuilder instance from environment variables
*/
Expand All @@ -45,10 +53,47 @@ export class ClientBuilder {
if (envConfig.headers) {
builder.withHeaders(envConfig.headers);
}
if (envConfig.userAgent) {
builder.withUserAgent(envConfig.userAgent);
}

return builder;
}

/**
* Create a ClientBuilder instance from a configuration object
*/
static fromConfig(config: ClientConfig): ClientBuilder {
const builder = new ClientBuilder().withApiKey(config.apiKey);

if (config.baseUrl) {
builder.withBaseUrl(config.baseUrl);
}
if (config.timeout !== undefined) {
builder.withTimeout(config.timeout);
}
if (config.maxRetries !== undefined) {
builder.withMaxRetries(config.maxRetries);
}
if (config.headers) {
builder.withHeaders(config.headers);
}
if (config.userAgent) {
builder.withUserAgent(config.userAgent);
}

return builder;
}

/**
* Set a custom user agent string
*/
withUserAgent(userAgent: string): this {
this.#validateString("userAgent", userAgent);
this.#config.userAgent = userAgent;
return this;
}

/**
* Set the API key for authentication
*/
Expand Down
Loading