npm package: @reinvented/sdk
Description: Reinvented Platform SDK — error reporting for Reinvented apps
Latest version: 0.4.0

# @reinvented/sdk

Official SDK for building apps on the Reinvented platform. Two entry points — one for server, one for client.

```
@reinvented/sdk/server  ← use in apps/api/
@reinvented/sdk/client  ← use in apps/desktop/ and apps/mobile/
```

## Installation

```bash
pnpm add @reinvented/sdk
```

Already installed in the app template for all three apps (api, desktop, mobile).

---

## Server SDK (`@reinvented/sdk/server`)

Use in your API backend (`apps/api/`). Reads environment variables automatically.

```js
import { createServerSDK, initErrorReporting } from '@reinvented/sdk/server';

const platform = createServerSDK();  // zero-config, reads from env

const app = express();
// ... routes ...
initErrorReporting(app);  // DO NOT DELETE — reports errors to platform
app.listen(4000);
```

### Environment Variables (auto-read)

| Variable | Required | Description |
|---|---|---|
| `PLATFORM_SERVICE_TOKEN` | Yes | Per-app authentication token |
| `PLATFORM_API_URL` | No | API URL (defaults to `https://api.reinvented.com`) |

Both are automatically injected into every app's environment during provisioning.

### Explicit Config (optional)

```js
const platform = createServerSDK({
  serviceToken: 'my-token',
  platformUrl: 'https://api.reinvented.com',
});
```

---

## Client SDK (`@reinvented/sdk/client`)

Use in your frontend apps (`apps/desktop/`, `apps/mobile/`).

```jsx
// main.jsx — just importing auto-installs error reporting
import '@reinvented/sdk/client';
```

If you also need to call platform services from the client:

```js
import { createClientSDK } from '@reinvented/sdk/client';

const platform = createClientSDK({
  serviceToken: 'your-token',  // required — browsers have no env vars
});
```

---

## Services

Both server and client SDKs provide the same three services:

### `platform.storage` — File Storage (Google Cloud Storage)

#### `storage.importFromUrl(url, options?)`

Download a file from any URL and store it on the platform CDN.

| Param | Type | Description |
|---|---|---|
| `url` | `string` | Source URL to download from |
| `options.contentType` | `string?` | Override content type |
| `options.extension` | `string?` | Override file extension |

**Returns:** `{ url: string, id: string, objectName: string }`

```js
const { url } = await platform.storage.importFromUrl('https://example.com/photo.jpg');
// url → 'https://storage.reinvented.cloud/<uuid>/original.jpg'
```

---

### `platform.email` — Transactional Email (Amazon SES)

#### `email.send({ userId, subject, html?, text? })`

Send an email to a platform user. The platform resolves their email address from the user ID.

| Param | Type | Description |
|---|---|---|
| `userId` | `string` | Platform user ID (not an email address) |
| `subject` | `string` | Email subject line |
| `html` | `string?` | HTML email body |
| `text` | `string?` | Plain text body (auto-generated from HTML if omitted) |

**Returns:** `{ ok: boolean, messageId: string }`

Emails are sent from `"App Name <appslug@reinvented.com>"`.

```js
await platform.email.send({
  userId: 'abc-123',
  subject: 'Your report is ready',
  html: '<h1>Weekly Report</h1><p>Click below to view.</p>',
});
```

---

### `platform.ai` — AI Tools (Google Gemini)

Default model: `gemini-2.5-flash`. All inference runs server-side on the platform.

#### `ai.generateJSON({ prompt, schema?, model?, temperature? })` ⭐ Primary

Generate structured JSON from a prompt. This is the most commonly used AI method.

| Param | Type | Default | Description |
|---|---|---|---|
| `prompt` | `string` | — | What to generate |
| `schema` | `object?` | — | Gemini-format JSON schema |
| `model` | `string?` | `gemini-2.5-flash` | Gemini model ID |
| `temperature` | `number?` | `0.7` | 0.0 = deterministic, 1.0 = creative |

**Returns:** Parsed JSON matching the schema.

**Schema types:** `STRING`, `NUMBER`, `INTEGER`, `BOOLEAN`, `ARRAY`, `OBJECT`

```js
const product = await platform.ai.generateJSON({
  prompt: 'Extract: "Nike Air Max 90, Size 10, $129.99"',
  schema: {
    type: 'OBJECT',
    properties: {
      name: { type: 'STRING' },
      size: { type: 'STRING' },
      price: { type: 'NUMBER' },
    },
  },
});
// → { name: 'Nike Air Max 90', size: '10', price: 129.99 }
```

#### `ai.generateText({ prompt, model?, temperature?, maxTokens? })`

Generate free-form text.

**Returns:** `{ text: string }`

```js
const { text } = await platform.ai.generateText({
  prompt: 'Write a welcome email for a new user named Sarah',
});
```

#### `ai.generateImage({ prompt, aspectRatio? })`

Generate an image. Auto-uploaded to platform storage.

**Returns:** `{ url: string, id: string, objectName: string }`

```js
const { url } = await platform.ai.generateImage({
  prompt: 'A minimalist logo for a health tech startup',
  aspectRatio: '1:1',
});
```

---

## Error Handling

All SDK methods throw `PlatformSDKError` on failure:

```js
import { PlatformSDKError } from '@reinvented/sdk/server';
// or: import { PlatformSDKError } from '@reinvented/sdk/client';

try {
  await platform.ai.generateJSON({ prompt: '...' });
} catch (err) {
  if (err instanceof PlatformSDKError) {
    console.error(err.message);  // Human-readable error description
    console.error(err.code);     // Machine-readable error code (see table below)
    console.error(err.status);   // HTTP status code (0 for network errors)
  }
}
```

| Error Code | Meaning | Common Cause |
|---|---|---|
| `MISSING_TOKEN` | No service token provided | Forgot to set `PLATFORM_SERVICE_TOKEN` or pass `serviceToken` |
| `NETWORK_ERROR` | Can't reach the platform API | Network issue, wrong `PLATFORM_API_URL`, platform down |
| `API_ERROR` | Platform returned an error | Bad request data, rate limit, server error (check `status` and `message`) |
| `INVALID_RESPONSE` | Platform returned non-JSON | Platform bug or network corruption |

---

## Error Reporting

The SDK includes automatic error reporting that catches uncaught errors and sends them to the platform for investigation. This is how the platform knows when an app is broken.

### How It Works

#### Server (`@reinvented/sdk/server`)

Error reporting operates at **two levels**:

1. **Process-level handlers** (automatic on import):
   - `uncaughtException` → logs, reports to platform, exits with code 1
   - `unhandledRejection` → logs, reports to platform, continues running

   These are installed the moment you `import` anything from `@reinvented/sdk/server`. No setup required. They catch errors that escape Express (startup errors, async code outside request handlers, etc.).

2. **Express middleware** (requires `initErrorReporting(app)`):
   - Catches errors thrown in Express route handlers and middleware
   - Reports each error with the HTTP method and path (e.g., `POST /graphql`)
   - Calls `next(err)` so your own error handler still runs

   ```js
   // MUST be added AFTER all routes and other middleware
   initErrorReporting(app);
   ```

`initErrorReporting()` reads `PLATFORM_API_URL` and `APP_SLUG` from env vars. If the slug is missing, it silently no-ops (safe for local dev without platform env). **No auth token is required** — the platform API accepts error reports without authentication.

#### Client (`@reinvented/sdk/client`)

Error reporting is fully automatic on import. It installs three handlers:

1. **`window.error`** — catches uncaught runtime errors (e.g., `TypeError: Cannot read property 'x' of null`)
2. **`window.unhandledrejection`** — catches unhandled promise rejections
3. **`console.error` interception** — catches framework-swallowed errors (React, Vue, etc. often catch errors and log them via `console.error` instead of letting them propagate)

Errors are sent directly to the platform API via `fetch('https://api.reinvented.com/api/app-errors')`. The app slug is derived from the URL path. No authentication is required.

The client includes built-in dedup (5 unique errors per session max) to avoid flooding the API during error loops.

This activates on any page — it no longer requires an iframe context.

```jsx
// main.jsx — just import, error reporting is active immediately
import '@reinvented/sdk/client';
```

### What Gets Reported

Every error report includes:

| Field | Description |
|---|---|
| `message` | Error message (truncated to 500 chars) |
| `stack` | Stack trace (truncated to 4000 chars) |
| `url` / `context` | Where the error occurred (server: HTTP method + path; client: page URL) |
| `appSlug` | Which app had the error |

### Error Reporting Flow

```
App Error
    │
    ├─ Server ──→ POST /api/app-errors ──→ Platform stores + deduplicates
    │              (no auth required)
    │
    └─ Client ──→ POST /api/app-errors ──→ Platform stores + deduplicates
                   (no auth required, app slug from URL)
```

Errors are deduplicated by fingerprint (SHA-256 of message + first stack frame). The platform's autonomous bug-fix agent uses MCP tools to triage, investigate, and resolve errors.

---

## Architecture

```
Your App                     Platform API              Cloud Services
─────────                    ────────────              ──────────────
                                                       
platform.storage.import()  → POST /sdk/storage/import → Google Cloud Storage
platform.email.send()      → POST /sdk/email/send     → Amazon SES
platform.ai.generateJSON() → POST /sdk/ai/generate-json → Google Gemini
platform.ai.generateText() → POST /sdk/ai/generate-text → Google Gemini
platform.ai.generateImage()→ POST /sdk/ai/generate-image → Gemini + GCS
```

The SDK is a thin HTTP client. All heavy lifting happens server-side on the platform API:
- **No cloud credentials** needed in your app
- **No dependencies** — zero npm dependencies
- **Works everywhere** — anywhere `fetch` exists