npm package: @nextplus/js-sdk
Description: A TypeScript SDK for interacting with the NextPlus API, automatically generated from OpenAPI specifications.
Latest version: 0.0.12

# @nextplus/js-sdk

A TypeScript SDK for interacting with the NextPlus API, automatically generated from OpenAPI specifications.

## Installation

```bash
npm install @nextplus/js-sdk
```

## Usage

### Authentication Methods

The SDK supports two authentication methods:

1. **Token-based authentication** - Use an existing access token (recommended for React/browser apps)
2. **Credential-based authentication** - Provide email/username + password (SDK handles login automatically)

### Token-Based Authentication (Recommended for React/Browser Apps)

Perfect for apps where authentication is handled separately (e.g., OAuth, Firebase Auth, custom auth):

```typescript
import { configure } from '@nextplus/js-sdk';

// With explicit baseURL
const sdk = configure({
  baseURL: 'https://your-nextplus-instance.com',
  accessToken: 'your-existing-token',
  returnType: 'data', // 'data' | 'raw'
});

// Auto-detect baseURL in browser (uses window.location.origin + '/api')
const sdk = configure({
  accessToken: userToken, // from your auth provider
  returnType: 'data',
});

// Use the SDK
const tables = await sdk.TableService.find({});
```

### Credential-Based Authentication

The SDK automatically handles login and token refresh:

```typescript
import { configure } from '@nextplus/js-sdk';

// With email
const sdk = configure({
  baseURL: 'https://your-nextplus-instance.com',
  email: 'your-email@example.com',
  password: 'your-password',
  returnType: 'data',
});

// With username
const sdk = configure({
  baseURL: 'https://your-nextplus-instance.com',
  username: 'your-username',
  password: 'your-password',
  returnType: 'data',
});
```

### Return Types

- **`'data'`**: Returns only the response data (default behavior)
- **`'raw'`**: Returns the full response object with `{ data, request, response }`

**Important:** The `returnType` setting only applies when using service methods through the SDK instance (e.g., `sdk.TableService.find()`). When using `sdk.client` directly with individual service imports, responses are always in raw format (`{ data, request, response }`).

### Error Handling

**By default**, non-OK responses return `{ error: ... }` instead of throwing:

```typescript
const response = await TableService.find({ limit: 10 });

if ('error' in response) {
  console.error('API Error:', response.error);
} else {
  console.log('Success:', response.data);
}
```

**To make errors throw automatically**, use `throwOnError: true`:

```typescript
configure({
  accessToken: token,
  throwOnError: true, // Errors will throw
});

try {
  const response = await TableService.find({ limit: 10 });
  // Only success responses reach here
} catch (error) {
  // All API errors throw here
  console.error('API Error:', error);
}
```

| Configuration                   | Error Behavior              |
| ------------------------------- | --------------------------- |
| `returnType: 'data'` (Full SDK) | ✅ Throws errors            |
| `returnType: 'raw'` (Full SDK)  | ❌ Returns `{ error: ... }` |
| `throwOnError: true`            | ✅ Throws errors            |
| Default (tree-shakeable)        | ❌ Returns `{ error: ... }` |

### Usage Patterns

**All patterns use the same `configure()` function!**

#### Pattern 1: Full SDK (Convenient, with return type control)

```typescript
import { configure } from '@nextplus/js-sdk';

const sdk = configure({
  accessToken: userToken,
  returnType: 'data', // Returns just the data
});

// Use service methods through SDK - returns transformed data
const tables = await sdk.TableService.find({ limit: 10 });
// tables = [{ ... }, { ... }] - just the array
```

#### Pattern 2a: Automatic Global Client (Cleanest - Recommended)

```typescript
import { configure, TableService, UserModelService } from '@nextplus/js-sdk';

// Configure once - automatically becomes global default!
configure({
  accessToken: userToken,
  throwOnError: true, // Make errors throw (optional)
});

// Use anywhere in your app - no client parameter needed!
try {
  const response = await TableService.find({ limit: 10 });
  const user = await UserModelService.findById({ path: { id: '123' } });

  // Returns raw response format
  console.log(response.data); // The actual data
} catch (error) {
  // With throwOnError: true, errors are thrown
  console.error('API Error:', error);
}
```

**Benefits:**

- ✅ Automatic - `configure()` sets global client automatically
- ✅ No client parameter needed anywhere
- ✅ Tree-shakeable - only import services you use
- ✅ Optional error throwing with `throwOnError: true`
- ✅ Clean code everywhere in your app

#### Pattern 2b: Bound Services (Per-component)

```typescript
import { configure, TableService, UserModelService } from '@nextplus/js-sdk';

// Configure once
const sdk = configure({ accessToken: userToken });

// Bind services - no need to pass client every time!
const Tables = sdk.service(TableService);
const Users = sdk.service(UserModelService);

// Use bound services
const response = await Tables.find({ limit: 10 });
const user = await Users.findById({ path: { id: '123' } });
```

#### Pattern 2c: Manual Client (Explicit control)

```typescript
const sdk = configure({ accessToken: userToken });

// Pass client explicitly each time
const response = await TableService.find({
  client: sdk.client,
  limit: 10,
});
```

#### Pattern 3: Manual Client Setup (Maximum control)

```typescript
import { TableService } from '@nextplus/js-sdk/services';
import { createClient } from '@nextplus/js-sdk/sdk/client';

const client = createClient({
  baseUrl: window.location.origin + '/api',
});

// Add auth interceptor manually
client.interceptors.request.use((req) => {
  req.headers.set('Authorization', myToken);
  return req;
});

const response = await TableService.find({ client });
```

**Comparison:**

| Feature             | Pattern 1: Full SDK | Pattern 2: Tree-shakeable | Pattern 3: Manual |
| ------------------- | ------------------- | ------------------------- | ----------------- |
| Function            | `configure()`       | `configure()`             | `createClient()`  |
| Service access      | `sdk.ServiceName.*` | Direct `ServiceName.*`    | Direct            |
| Bundle size         | Larger              | Smaller                   | Smallest          |
| Return type control | ✅ `'data'`/`'raw'` | ❌ Always raw             | ❌ Always raw     |
| Auto-login          | ✅ (credentials)    | ✅ (credentials)          | ❌                |
| Complexity          | Simple              | Simple                    | More control      |
| Best for            | Full apps           | Modern bundlers           | Advanced use      |

### Automatic Global Client

**`configure()` automatically sets the global default client** - no manual setup needed!

```typescript
import { configure, TableService, UserModelService } from '@nextplus/js-sdk';

// Configure once - automatically becomes global default!
configure({ accessToken: token });

// Use anywhere in your app - no client needed!
const tables = await TableService.find({ limit: 10 });
const users = await UserModelService.find({ limit: 10 });

// No client parameter needed! ✨
```

**Use Cases:**

```typescript
// React app initialization
function App() {
  useEffect(() => {
    // configure() automatically sets global client
    configure({ accessToken: getAuthToken() });

    return () => clearDefaultClient(); // Cleanup on unmount
  }, []);

  return <Routes />;
}

// Any component can now use services
function TableList() {
  const [tables, setTables] = useState([]);

  useEffect(() => {
    // No client parameter needed!
    TableService.find({ limit: 100 }).then(res => setTables(res.data));
  }, []);

  return <div>{/* render tables */}</div>;
}
```

**Manual Control (Optional):**

```typescript
// Set global client manually
setDefaultClient(client);

// Get current global client
const client = getDefaultClient();

// Clear global client (useful for logout)
clearDefaultClient();
```

**Client Override:**

You can still pass a `client` parameter to override the global default:

```typescript
configure({ accessToken: token1 }); // Sets global client

// Uses global client
await TableService.find({ limit: 10 });

// Uses specific client (overrides global)
await TableService.find({ client: customClient, limit: 10 });
```

### Interceptors (Request/Response Middleware)

Add custom logic to all requests and responses:

```typescript
import { configure } from '@nextplus/js-sdk';

const sdk = configure({ accessToken: token });

// Request interceptor - runs before every request
sdk.client.interceptors.request.use((request) => {
  console.log('Request:', request.url);
  // Add custom headers
  request.headers.set('X-Custom-Header', 'value');
  // Modify request
  return request;
});

// Response interceptor - runs after every successful response
sdk.client.interceptors.response.use((response) => {
  console.log('Response:', response.status);
  // Log or transform responses
  return response;
});

// Error interceptor - runs on any error
sdk.client.interceptors.error.use((error) => {
  console.error('API Error:', error);
  // Custom error handling, logging, etc.
  throw error; // Re-throw or handle
});
```

**Common Use Cases:**

```typescript
// Add request ID to all requests
sdk.client.interceptors.request.use((request) => {
  request.headers.set('X-Request-ID', crypto.randomUUID());
  return request;
});

// Add retry logic
sdk.client.interceptors.error.use(async (error) => {
  if (error.response?.status === 429) {
    // Rate limited - wait and retry
    await new Promise((resolve) => setTimeout(resolve, 1000));
    // Retry logic here
  }
  throw error;
});

// Log all API calls
sdk.client.interceptors.request.use((request) => {
  console.log(`[API] ${request.method} ${request.url}`);
  return request;
});
```

**Works with all patterns:**

```typescript
// Pattern 1: Full SDK
const sdk = configure({ accessToken: token });
sdk.client.interceptors.request.use(/* ... */);
await sdk.TableService.find({});

// Pattern 2: Tree-shakeable
const Tables = sdk.service(TableService);
await Tables.find({}); // Interceptors still apply!

// Pattern 3: Manual
await TableService.find({ client: sdk.client }); // Interceptors apply!
```

### API Usage Examples

```typescript
// Find with filters
const tables = await sdk.TableService.find({
  where: { name: 'my-table' },
  limit: 10,
  order: 'createdAt DESC',
});

// Find with filter object
const tables = await sdk.TableService.find({
  filter: {
    where: { name: 'my-table' },
    limit: 10,
  },
});

// Create a record
const newTable = await sdk.TableService.create({
  name: 'New Table',
  description: 'Table description',
});

// Update a record
const updated = await sdk.TableService.prototypePatchAttributes({
  path: { id: 'table-id' },
  name: 'Updated Name',
});

// With raw response type
const sdk = configure({
  accessToken: 'token',
  returnType: 'raw',
});

const response = await sdk.TableService.find({});
console.log(response.data); // Response data
console.log(response.request); // Request object
console.log(response.response); // Full HTTP response
```

## Development

### Prerequisites

- Node.js (v18 or higher)
- npm

### Setup

```bash
# Install dependencies
npm install

# Generate SDK from OpenAPI spec and build
npm run generate
```

### Available Scripts

- **`npm run generate`**: Clean, generate from OpenAPI spec, and build
- **`npm run build`**: Build TypeScript to JavaScript with type declarations
- **`npm run clean`**: Remove the `dist` folder
- **`npm run openapi-ts`**: Generate SDK from OpenAPI specification
- **`npm run lint`**: Run ESLint

### Development Workflow

1. **Make changes** to the source code in `src/`
2. **Generate and build**: `npm run generate`
3. **Test changes**: `npm run test` (run smoke tests)
4. **Publish**: `npm publish` (see Publishing section)

### Code Generation

The SDK is automatically generated from OpenAPI specifications using `@hey-api/openapi-ts`. The generated code includes:

- Type-safe API clients
- Request/response type definitions
- Authentication handling
- Error handling

Generated files are located in `src/sdk/` and should not be manually edited.

## Testing

### Test Commands

```bash
# Run full test suite (builds first, then runs tests)
npm test

# Run tests directly from TypeScript (faster for development)
npm run test:dev

# Run tests in watch mode (re-runs on file changes)
npm run test:watch
```

### Test Suite

Make sure your NextPlus instance is running on `localhost:3000` before running tests.

The test suite (`src/smoke.ts`) uses Node's built-in test runner and validates:

- Token-based authentication
- Email and username authentication
- Different return types (`data` vs `raw`)
- Tree-shakeable service imports
- API calls to the TableService
- Configuration validation and error handling

### Manual Testing

```typescript
// Test with token authentication
import { configure } from '@nextplus/js-sdk';

const sdk = configure({
  baseURL: 'http://localhost:3000',
  accessToken: 'your-test-token',
});

const result = await sdk.TableService.find({});
console.log(result);

// Test with credentials
const sdk2 = configure({
  baseURL: 'http://localhost:3000',
  email: 'test@example.com',
  password: 'password',
});

const result2 = await sdk2.TableService.find({});
console.log(result2);

// Test tree-shakeable imports
import { configure, TableService } from '@nextplus/js-sdk';

const sdk = configure({
  accessToken: 'your-test-token',
});

const tables = await TableService.find({ client: sdk.client });
console.log(tables);
```

## Publishing

### Version Management

Update the version in `package.json` and publish:

```bash
# Bump version (patch, minor, or major)
npm version patch   # 0.0.3 -> 0.0.4
npm version minor   # 0.0.3 -> 0.1.0
npm version major   # 0.0.3 -> 1.0.0

# Publish to npm
npm publish
```

The `prepublishOnly` script automatically cleans and builds before publishing.

### Publishing Steps

1. **Ensure you're logged in**: `npm whoami`
2. **Build**: `npm run build` (optional, done automatically)
3. **Publish**: `npm publish`

## Project Structure

```
├── src/
│   ├── index.ts           # Main SDK entry point
│   ├── smoke.ts           # Smoke test file
│   └── sdk/               # Generated SDK code (do not edit)
│       ├── client/        # API client implementations
│       ├── core/          # Core utilities and types
│       └── *.gen.ts       # Generated files
├── dist/                  # Built JavaScript and declarations
├── package.json
├── tsconfig.json
└── README.md
```

## Configuration

### OpenAPI Generation

Configure OpenAPI generation in `openapi-ts.config.ts`:

```typescript
export default {
  input: 'path/to/openapi.json',
  output: './src/sdk',
  // ... other options
};
```

### TypeScript

The project uses modern TypeScript with:

- ES2020 target
- ESNext modules
- Strict type checking
- Declaration file generation

## License

MIT

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Run tests: `npm run test`
5. Build: `npm run build`
6. Submit a pull request

## Support

For issues and questions, please create an issue in the repository or contact the NextPlus team.