# Storage

Two services, both verified against the bundled `Rudder.Core.dll` and
`Samples~/Storage/StorageSample.cs`.

## Player storage — `client.Storage` (`StorageService`)

Per-player key-value items. `StorageItem` is `{ Type, Id, Data }` where
`Data` is an opaque JSON string — serialize your save object with
`JsonUtility` or Newtonsoft; do not invent a second save format.

- `Task<GetStorageResponse> GetAsync(string type = null, int limit = 100, string cursor = null, CancellationToken = default)` — pages items; response has `Items` (`List<StorageItem>`) and `NextCursor`.
- `IAsyncEnumerable<StorageItem> ListAllAsync(string type = null, int limit = 100, CancellationToken = default)` — follows cursors for you.
- `Task SaveAsync(IEnumerable<StorageItem> items, CancellationToken = default)` — upserts by `Type`+`Id`.
- `Task DeleteAsync(string type, CancellationToken = default)` — deletes all items of that type.

```csharp
await client.Storage.SaveAsync(new[]
{
    new StorageItem { Type = "sample_save", Id = "main", Data = json }
});
var response = await client.Storage.GetAsync("sample_save");
await client.Storage.DeleteAsync("sample_save");
```

## Project storage — `client.ProjectStorage` (`ProjectStorageService`)

Global key-value storage shared across players (read from the SDK; writes
respect item permissions).

- `Task<GetProjectStorageResponse> GetAsync(string type = null, int limit = 100, string cursor = null, CancellationToken = default)` — `Items` + `NextCursor`.
- `IAsyncEnumerable<ProjectStorageItem> ListAllAsync(string type = null, int limit = 100, CancellationToken = default)`
- `Task SaveAsync(IEnumerable<ProjectStorageUpdateItem> items, string idempotencyKey = null, CancellationToken = default)`

`ProjectStorageItem`: `Type`, `Id`, `Data` (JSON string), `ReadPermission`,
`WritePermission`, `Version` (`long?`), `Size` (`long?`), `ExpiresAt`,
`UpdatedAt`. `ProjectStorageUpdateItem`: `{ Type, Data }`.
