npm package: @nuucognition/mesh-convex-component
Description: Convex components for Mesh backend storage. Defines: - an **operational** component (`mesh`) for operations-first mutations, and - a **snapshot** component (`meshSnapshot`) for full-replace snapshot commits.
Latest version: 1.0.2

# @nuucognition/mesh-convex-component

Convex components for Mesh backend storage. Defines:
- an **operational** component (`mesh`) for operations-first mutations, and
- a **snapshot** component (`meshSnapshot`) for full-replace snapshot commits.

## Architecture

This package has three parts:

1. **Operational Convex component** (`src/component/`) — schema + operations-first server functions
2. **Snapshot Convex component** (`src/component-snapshot/`) — schema + full snapshot commit server functions
3. **Client classes** (`src/client/`) — `MeshConvex` and `MeshConvexSnapshot`

## Install the component

In your Convex app's `convex/convex.config.ts`:

```ts
import { defineApp } from "convex/server"
import mesh from "@nuucognition/mesh-convex-component/convex.config.js"
import meshSnapshot from "@nuucognition/mesh-convex-component/snapshot/convex.config.js"

const app = defineApp()
app.use(mesh)
app.use(meshSnapshot)

export default app
```

## Use the client

```ts
import { MeshConvex } from "@nuucognition/mesh-convex-component"
import { MeshConvexSnapshot } from "@nuucognition/mesh-convex-component"
import { components } from "./convex/_generated/api"

const client = new MeshConvex({ component: components.mesh })
const snapshotClient = new MeshConvexSnapshot({ component: components.meshSnapshot })

// In a Convex query/mutation handler:
const raw = await client.loadRaw(ctx, "my-mesh-id")
await client.applyOperations(ctx, "my-mesh-id", [
  {
    type: "create",
    title: "Note Title",
    content: "# Content",
  },
])

await snapshotClient.commitSnapshot(ctx, "my-mesh-id", {
  documents: [{ title: "Doc", content: "# Snapshot" }],
  blobs: [],
})
```

## Schema

The component creates four tables (prefixed with `mesh:` in the dashboard):

| Table | Fields | Indexes |
|-------|--------|---------|
| `meshes` | `id`, `name?`, `transactionSeq`, `createdAt`, `updatedAt` | `by_mesh_id` |
| `documents` | `meshId`, `title`, `content`, `createdAt`, `updatedAt` | `by_mesh`, `by_mesh_title` |
| `blobs` | `meshId`, `blobKey`, `storageId`, `createdAt`, `updatedAt` | `by_mesh`, `by_mesh_blobKey` |
| `transactions` | `meshId`, `transactionId`, `seq`, `timestamp`, `author?`, `source`, `intent?`, `historyKind?`, `historyTargetSeq?`, `reversible?`, `nonReversibleReasons?`, `documentTitles`, `operationsJson`, `createdAt` | `by_mesh`, `by_mesh_seq`, `by_mesh_transaction_id` |

## Component API

### Meshes

| Function | Type | Args | Returns |
|----------|------|------|---------|
| `meshes.create` | mutation | `{ id, name? }` | `string` |
| `meshes.get` | query | `{ id }` | mesh object or `null` |
| `meshes.list` | query | `{}` | mesh objects array |
| `meshes.exists` | query | `{ id }` | `boolean` |
| `meshes.remove` | mutation | `{ id }` | `boolean` (cascades docs + blobs) |

### Documents

| Function | Type | Args | Returns |
|----------|------|------|---------|
| `documents.save` | mutation | `{ meshId, title, content }` | `null` (upsert) |
| `documents.list` | query | `{ meshId }` | `{ title, updatedAt }[]` |
| `documents.getContent` | query | `{ meshId, title }` | `string` or `null` |
| `documents.getContents` | query | `{ meshId, titles }` | `{ title, content }[]` |
| `documents.remove` | mutation | `{ meshId, title }` | `boolean` |

### Blobs

| Function | Type | Args | Returns |
|----------|------|------|---------|
| `blobs.save` | mutation | `{ meshId, blobKey, storageId }` | `null` (upsert) |
| `blobs.list` | query | `{ meshId }` | `{ blobKey, updatedAt }[]` |
| `blobs.getUrl` | query | `{ meshId, blobKey }` | `string` or `null` |
| `blobs.generateUploadUrl` | mutation | `{}` | `string` |
| `blobs.remove` | mutation | `{ meshId, blobKey }` | `boolean` |
| `blobs.deleteStorageObject` | mutation | `{ storageId }` | `null` |

### Operations

| Function | Type | Args | Returns |
|----------|------|------|---------|
| `operations.applyOperations` | mutation | `{ meshId, operationsJson, enableTransactionJournal?, transaction? }` | `null` |

### Transactions

| Function | Type | Args | Returns |
|----------|------|------|---------|
| `transactions.clear` | mutation | `{ meshId }` | `number` |
| `transactions.getCurrentSeq` | query | `{ meshId }` | `number` |
| `transactions.getStatus` | query | `{ meshId }` | `{ seq, journalStateHash, currentStateHash, unobservedChanges, lastTransactionSeq? }` |
| `transactions.sync` | mutation | `{ meshId, author?, ensureIds?, healIntent?, reconcileIntent? }` | sync summary |
| `transactions.getSince` | query | `{ meshId, seq }` | transaction array |
| `transactions.getRange` | query | `{ meshId, from, to }` | transaction array |
| `transactions.getLatest` | query | `{ meshId, n }` | transaction array |
| `transactions.getByDocument` | query | `{ meshId, title, limit? }` | transaction array |

### Snapshot

| Function | Type | Args | Returns |
|----------|------|------|---------|
| `snapshots.commit` | mutation | `{ meshId, name?, documents, blobs }` | `null` |

## Client API (`MeshConvex`)

| Method | Context | Description |
|--------|---------|-------------|
| `loadRaw(ctx, meshId)` | query | Load all documents + blob keys for a mesh |
| `getContent(ctx, meshId, title)` | query | Get single document content (throws if missing) |
| `getBlobData(ctx, meshId, blobKey)` | query | Fetch blob binary via storage URL |
| `getUploadUrl(ctx)` | mutation | Get signed URL for blob upload |
| `saveBlobFromStorageId(ctx, meshId, blobKey, storageId)` | mutation | Save blob record after upload |
| `deleteStorageObject(ctx, storageId)` | mutation | Delete raw uploaded storage object |
| `applyOperations(ctx, meshId, operations)` | mutation | Apply an operations-first mutation batch atomically |
| `getTransactionStatus(ctx, meshId)` | query | Get journal/current state hashes + unobserved-change flag |
| `syncTransactions(ctx, meshId, options?)` | mutation | Heal missing UUIDs and append inferred reconcile transaction(s) |

## Client API (`MeshConvexSnapshot`)

| Method | Context | Description |
|--------|---------|-------------|
| `loadRaw(ctx, meshId)` | query | Load all documents + blob keys for a mesh |
| `getContent(ctx, meshId, title)` | query | Get single document content (throws if missing) |
| `getBlobData(ctx, meshId, blobKey)` | query | Fetch blob binary via storage URL |
| `getUploadUrl(ctx)` | mutation | Get signed URL for blob upload |
| `deleteStorageObject(ctx, storageId)` | mutation | Delete raw uploaded storage object |
| `commitSnapshot(ctx, meshId, payload)` | mutation | Replace full mesh snapshot atomically |

## Exports

| Path | Contents |
|------|----------|
| `@nuucognition/mesh-convex-component` | `MeshConvex`, `MeshConvexSnapshot`, factories, types |
| `@nuucognition/mesh-convex-component/convex.config.js` | Component definition for `app.use()` |
| `@nuucognition/mesh-convex-component/snapshot/convex.config.js` | Snapshot component definition for `app.use()` |
| `@nuucognition/mesh-convex-component/schema` | Table schema for `convex-test` |
| `@nuucognition/mesh-convex-component/snapshot/schema` | Snapshot table schema for `convex-test` |
| `@nuucognition/mesh-convex-component/test` | Test utilities |

## Package layout

```
src/
  client/           MeshConvex class and types
  component/        Convex component (schema, meshes, documents, blobs)
    _generated/     Auto-generated Convex files
    convex.config.ts
    schema.ts
    meshes.ts
    documents.ts
    blobs.ts
    transactions.ts
  test.ts           Test utilities
  index.ts          Public exports
```

## Related packages

- `@nuucognition/mesh-core` — core types and parsing
- `@nuucognition/mesh-adapter-convex` — bridge that adds mesh-core parsing on top of this client
- `@nuucognition/mesh-integration-tests` — E2E tests for the full pipeline