npm package: @intrface/solidparty-sdk
Description: A tiny TypeScript client for talking to **Solidparty** (our Copyparty + S3 storage gateway) from **Convex backends** and other TypeScript servers.
Latest version: 0.0.1

# @intrface/solidparty-sdk

A tiny TypeScript client for talking to **Solidparty** (our Copyparty + S3 storage gateway) from **Convex backends** and other TypeScript servers.

- ✅ Safe for **server-side** use (Convex actions / server routes)
- ✅ No browser secrets (admin credentials never leave the backend)
- ✅ Simple, file-oriented API: `upload`, `delete`, `list`

> IMPORTANT: Do **not** import this SDK in client/browser code. Use it only from Convex `action` / `internalAction` / server-side contexts.

---

## Installation

From your monorepo root (homebase):

```bash
pnpm install
pnpm --filter @intrface/solidparty-sdk build
```

To publish to your **private npm registry**, bump the version in `package.json` and run your existing `npm publish` workflow for scoped packages.

---

## Configuration

Solidparty is configured via environment variables on the **Solidparty container**.
For Convex, you will set **matching env vars** so the SDK can connect to that instance.

Suggested Convex environment variables:

- `SOLIDPARTY_BASE_URL` – e.g. `https://solidparty.intrface.dev/public` or internal service DNS
- `SOLIDPARTY_ADMIN_USER` – admin username configured in `entrypoint.sh`
- `SOLIDPARTY_ADMIN_PASSWORD` – admin password (never exposed to client)

---

## Usage with Convex

### 1. Create a helper module in your Convex project

```ts
// convex/solidparty.ts
import { createSolidpartyClient } from "@intrface/solidparty-sdk";

export function solidparty() {
  const baseUrl = process.env.SOLIDPARTY_BASE_URL;
  const username = process.env.SOLIDPARTY_ADMIN_USER;
  const password = process.env.SOLIDPARTY_ADMIN_PASSWORD;

  if (!baseUrl || !username || !password) {
    throw new Error("Missing Solidparty env vars in Convex environment");
  }

  return createSolidpartyClient({
    baseUrl,
    username,
    password,
    // Convex exposes a global fetch implementation
    fetchImpl: fetch,
  });
}
```

### 2. Use from an internal Convex action

```ts
// convex/files.ts
import { internalAction } from "convex/server";
import { v } from "convex/values";
import { solidparty } from "./solidparty";

export const uploadReport = internalAction({
  args: {
    fileId: v.string(),
    // Raw bytes from previous pipeline step (e.g. generated PDF)
    bytes: v.bytes(),
    contentType: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const client = solidparty();

    const { url, path } = await client.upload({
      path: `reports/${args.fileId}.pdf`,
      body: new Uint8Array(args.bytes),
      contentType: args.contentType ?? "application/pdf",
    });

    // Store URL in Convex DB so frontends can access it
    await ctx.db.patch(args.fileId, {
      reportUrl: url,
      reportPath: path,
    });
  },
});
```

### 3. Deleting an object

```ts
export const deleteReport = internalAction({
  args: { path: v.string() },
  handler: async (_ctx, args) => {
    const client = solidparty();
    await client.delete(args.path); // e.g. "reports/abc123.pdf"
  },
});
```

### 4. Listing a directory

```ts
export const listPublicImages = internalAction({
  args: {},
  handler: async () => {
    const client = solidparty();
    return client.list({ prefix: "images" });
  },
});
```

---

## Data Safety & Backups

Solidparty itself is **stateless**: it forwards reads/writes to your S3-compatible bucket.
For durability and backup:

1. **Enable S3 Versioning** on the bucket used by Solidparty.
2. Optionally configure **Cross-Region Replication** to a second bucket.
3. Add **Lifecycle Rules** to move old versions to Glacier/Deep Archive for long-term storage.

Your Convex apps should treat Solidparty URLs as authoritative references, while relying on S3 for actual durability and recovery.

---

## API Reference

### `createSolidpartyClient(config)`

```ts
createSolidpartyClient({
  baseUrl: string;             // e.g. "https://solidparty.service/public"
  username: string;            // Solidparty admin username
  password: string;            // Solidparty admin password
  fetchImpl?: typeof fetch;    // Optional, defaults to globalThis.fetch
});
```

### `client.upload(params)`

```ts
await client.upload({
  path: string;                // "images/avatar-123.png"
  body: ArrayBuffer | Uint8Array;
  contentType?: string;        // e.g. "image/png"
});
```

Returns:

```ts
{
  url: string;                 // Full URL to the stored object
  path: string;                // Normalized storage path
  etag?: string;               // Optional ETag from upstream storage
}
```

### `client.delete(path)`

Deletes the object at the given path. 404s are treated as success.

### `client.list({ prefix? })`

Lists objects under a directory prefix. Requires Solidparty directory listing (JSON mode) to be enabled for the volume.

```ts
const items = await client.list({ prefix: "images" });
// items: { name, path, size, lastModified, url }[]
```

---

You can now point **any Convex backend** at Solidparty and have a consistent, documented storage connector without rebuilding the integration each time.