# 集合

**URL:** https://heroiclabs.com/docs/zh/nakama/concepts/storage/collections/
**Summary:** Nakama 为用户帐户等项目特有数据集成了一个存储引擎。对存储引擎设计进行了优化，从而实现对象所有权、访问权限和批处理操作。数据存储在具有一个或多个对象的集合中，这些对象包含具有 JSON 内容的唯一键。

---


# 集合

每个应用程序或游戏都有项目特有的数据。

必须为用户存储这些信息，必须对其进行更新、检索和在 UI 的各个部分内显示。为此，服务器中采用了具有优化设计的存储引擎，实现[对象所有权](../permissions/#object-ownership)、[访问权限](../permissions/#object-permissions)和批处理操作。

数据存储在具有一个或多个对象的集合中，这些对象包含具有 JSON 内容的唯一键。创建集合无需任何配置。这将创建一个简单的嵌套名称空间，表示对象的位置。

```bash {linenos=false}
Collection
+---------------------------------------------------------------------+
|  Object                                                             |
|  +----------+------------+-------------+-----+-------------------+  |
|  | ?UserId? | Identifier | Permissions | ... |       Value       |  |
|  +---------------------------------------------------------------+  |
+---------------------------------------------------------------------+
```

这种设计为开发者提供了很大的灵活性，让他们可以将游戏或应用程序中的信息集合分组。

{{< note "important" >}}
不鼓励编写自定义 SQL，而是推荐使用存储引擎的内置功能。如果您的用例需要自定义 SQL，请[与 Heroic Labs 联系](mailto:support@heroiclabs.com)后再继续。
{{< / note >}}

{{< note "error" >}}
强烈建议不要创建自定义表。
{{< / note >}}

## 写入对象

用户可以写入一个或多个对象，这些将被存储在数据库服务器中。这些对象将在单个事务中写入，以确保所有写入一举成功。

{{< code type="client" >}}
```bash
curl -X PUT "http://127.0.0.1:7350/v2/storage" \
  -H 'Authorization: Bearer <session token>' \
  -d '{"objects":
    [
      {
        "collection": "saves",
        "key": "savegame",
        "value": "{"progress": "50"}"
      },
      {
        "collection": "stats",
        "key": "skill",
        "value": "{"progress": "24"}"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var save_game = { "progress": 50 };
var my_stats = { "skill": 24 };

const object_ids = await client.writeStorageObjects(session, [
  {
    "collection": "saves",
    "key": "savegame",
    "value": save_game
  }, {
    "collection": "stats",
    "key": "skills",
    "value": my_stats
  }
]);

console.info("Successfully stored objects: ", object_ids);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var saveGame = "{ "progress": 50 }";
var myStats = "{ "skill": 24 }";

var writeObjects = new [] {
    new WriteStorageObject
    {
        Collection = "saves",
        Key = "savegame",
        Value = saveGame
    },
    new WriteStorageObject
    {
        Collection = "stats",
        Key = "skills",
        Value = myStats
    }
};

await client.WriteStorageObjectsAsync(session, writeObjects);
Console.WriteLine("Successfully stored objects: [{0}]", string.Join(",\n  ", objectIds));
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NStorageObjectAcks& acks)
{
    std::cout << "Successfully stored objects " << acks.size() << std::endl;
};

std::vector<NStorageObjectWrite> objects;
NStorageObjectWrite savesObject, statsObject;
savesObject.collection = "saves";
savesObject.key = "savegame";
savesObject.value = "{ "progress": 50 }";
objects.push_back(savesObject);

statsObject.collection = "stats";
statsObject.key = "skills";
statsObject.value = "{ "skill": 24 }";
objects.push_back(statsObject);
client->writeStorageObjects(session, objects, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String saveGame = "{ "progress": 50 }";
String myStats = "{ "skill": 24 }";
StorageObjectWrite saveGameObject = new StorageObjectWrite("saves", "savegame", saveGame, PermissionRead.OWNER_READ, PermissionWrite.OWNER_WRITE);
StorageObjectWrite statsObject = new StorageObjectWrite("stats", "skills", myStats, PermissionRead.OWNER_READ, PermissionWrite.OWNER_WRITE);
StorageObjectAcks acks = client.writeStorageObjects(session, saveGameObject, statsObject).get();
System.out.format("Stored objects %s", acks.getAcksList());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var save_game = "{ "progress": 50 }"
var my_stats = "{ "skill": 24 }"
var can_read = 1
var can_write = 1
var version = ""

var acks : NakamaAPI.ApiStorageObjectAcks = yield(client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new("saves", "savegame", can_read, can_write, save_game, version),
    NakamaWriteStorageObject.new("stats", "skills", can_read, can_write, my_stats, version)
]), "completed")

if acks.is_exception():
    print("An error occurred: %s" % acks)
    return

print("Successfully stored objects:")

for a in acks.acks:
    print("%s" % a)
```
{{< / code >}}

{{< code type="client" >}}
```shell
PUT /v2/storage
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "objects": [
    {
      "collection": "saves",
      "key": "key",
      "value": "{"hello": "world"}"
    },
    {
      "collection": "stats",
      "key": "skill",
      "value": "{"progress": "24"}"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local save_game = json.encode({ progress = 50 })
local my_stats = json.encode({ skill = 24 })
local can_read = 1
local can_write = 1
local version = ""

local objects = {
  {
				collection = "saves",
				key = "savegame",
				permissionRead = can_read,
				permissionWrite = can_write,
				value = save_game,
				version = version,
  },
  {
				collection = "stats",
				key = "skills",
				permissionRead = can_read,
				permissionWrite = can_write,
				value = my_stats,
				version = version,
  }
}

local result = client.write_storage_objects(objects)

if result.error then
  print(result.message)
  return
end

for _,ack in ipairs(result.acks) do
  pprint(ack)
end
```
{{< / code >}}

### 条件写入

成功存储对象后，将返回一个版本，该版本可用于进一步更新，以便在下次写入时执行并发修改检查。这称为条件写入。

条件写入可确保客户端仅在看到过对象的早期版本时才能更新该对象。这样做的目的是，如果另一个客户端在第一个客户端的读取和下一次写入之间更改了该值，则防止对该对象被更改。

{{< code type="client" >}}
```bash
curl -X PUT "http://127.0.0.1:7350/v2/storage" \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "objects": [
      {
        "collection": "saves",
        "key": "savegame",
        "value": "{"progress": "50"}",
        "version": "some-previous-version"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var save_game = { "progress": 50 };

const object_ids = await client.writeStorageObjects(session, [
  {
    "collection": "saves",
    "key": "savegame",
    "value": save_game,
    "version": "<version>"
  }
]);

console.info("Stored objects: %o", object_ids);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var saveGame = "{ "progress": 50 }";

var objectIds = await client.WriteStorageObjectsAsync(session, new [] { 
  new WriteStorageObject
  {
      Collection = "saves",
      Key = "savegame",
      Value = saveGame,
      Version = "<version>"
  }
});

Console.WriteLine("Stored objects: [{0}]", string.Join(",\n  ", objectIds));
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NStorageObjectAcks& acks)
{
    std::cout << "Successfully stored objects " << acks.size() << std::endl;
};

std::vector<NStorageObjectWrite> objects;
NStorageObjectWrite savesObject;
savesObject.collection = "saves";
savesObject.key = "savegame";
savesObject.value = "{ "progress": 50 }";
savesObject.version = "<version>";
objects.push_back(savesObject);
client->writeStorageObjects(session, objects, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String saveGame = "{ "progress": 50 }";
StorageObjectWrite object = new StorageObjectWrite("saves", "savegame", saveGame, PermissionRead.OWNER_READ, PermissionWrite.OWNER_WRITE);
object.setVersion("<version>");
StorageObjectAcks acks = client.writeStorageObjects(session, object).get();
System.out.format("Stored objects %s", acks.getAcksList());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var save_game = "{ "progress": 50 }"
var can_read = 1
var can_write = 1
var version = "<version>"

var acks : NakamaAPI.ApiStorageObjectAcks = yield(client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new("saves", "savegame", can_read, can_write, save_game, version)
]), "completed")

if acks.is_exception():
    print("An error occurred: %s" % acks)
    return

print("Successfully stored objects:")

for a in acks.acks:
    print("%s" % a)
```
{{< / code >}}

{{< code type="client" >}}
```shell
PUT /v2/storage
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "objects": [
    {
      "collection": "saves",
      "key": "key",
      "value": "{"hello": "world"}",
      "version": "<version>"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local save_game = json.encode({ progress = 50 })
local can_read = 1
local can_write = 1
local version = "some previous version"

local objects = {
  {
				collection = "saves",
				key = "savegame",
				permissionRead = can_read,
				permissionWrite = can_write,
				value = save_game,
				version = version,
  }
}

local result = client.write_storage_objects(objects)

if result.error then
  print(result.message)
  return
end

for _,ack in ipairs(result.acks) do
  pprint(ack)
end
```
{{< / code >}}

我们支持另一种条件写入，它仅在对象的集合和键不存在时才用于写入对象。

{{< code type="client" >}}
```bash
curl -X PUT "http://127.0.0.1:7350/v2/storage" \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "objects": [
      {
        "collection": "saves",
        "key": "savegame",
        "value": "{"progress": "50"}",
        "version": "*"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var save_game = { "progress": 50 };
const object_ids = await client.writeStorageObjects(session, [
  {
    "collection": "saves",
    "key": "savegame",
    "value": save_game,
    "version": "*"
  }
]);
console.info("Stored objects: %o", object_ids);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var saveGame = "{ "progress": 50 }";

var objectIds = await client.WriteStorageObjectsAsync(session, new [] {
  new WriteStorageObject
  {
      Collection = "saves",
      Key = "savegame",
      Value = saveGame,
      Version = "*"
  }
});

Console.WriteLine("Stored objects: [{0}]", string.Join(",\n  ", objectIds));
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NStorageObjectAcks& acks)
{
    std::cout << "Successfully stored objects " << acks.size() << std::endl;
};

std::vector<NStorageObjectWrite> objects;
NStorageObjectWrite savesObject;
savesObject.collection = "saves";
savesObject.key = "savegame";
savesObject.value = "{ "progress": 50 }";
savesObject.version = "*";
objects.push_back(savesObject);
client->writeStorageObjects(session, objects, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String saveGame = "{ "progress": 50 }";
StorageObjectWrite object = new StorageObjectWrite("saves", "savegame", saveGame, PermissionRead.OWNER_READ, PermissionWrite.OWNER_WRITE);
object.setVersion("*");
StorageObjectAcks acks = client.writeStorageObjects(session, object).get();
System.out.format("Stored objects %s", acks.getAcksList());
```
{{< / code >}}

{{< code type="client" >}}
```java
// Requires Nakama 1.x

String saveGame = "{"progress": 1}";
String version = "*"; // represents "no version".

CollatedMessage<ResultSet<RecordId>> message = StorageWriteMessage.Builder.newBuilder()
    .record("myapp", "saves", "savegame", saveGame, version)
    .build();

Deferred<ResultSet<RecordId>> deferred = client.send(message);

deferred.addCallback(new Callback<ResultSet<RecordId>, ResultSet<RecordId>>() {
    @Override
    public ResultSet<RecordId> call(ResultSet<RecordId> list) throws Exception {
        // Cache updated version for next write.
        version = list.getResults().get(0).getVersion();
        return list;
    }
}).addErrback(new Callback<Error, Error>() {
    @Override
    public Error call(Error err) throws Exception {
        System.err.format("Error('%s', '%s')", err.getCode(), err.getMessage());
        return err;
    }
});
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var save_game = "{ "progress": 50 }"
var can_read = 1
var can_write = 1
var version = "*" # represents "no version".

var acks : NakamaAPI.ApiStorageObjectAcks = yield(client.write_storage_objects_async(session, [
    NakamaWriteStorageObject.new("saves", "savegame", can_read, can_write, save_game, version)
]), "completed")

if acks.is_exception():
    print("An error occurred: %s" % acks)
    return

print("Successfully stored objects:")

for a in acks.acks:
    print("%s" % a)
```
{{< / code >}}

{{< code type="client" >}}
```shell
PUT /v2/storage
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "objects": [
    {
      "collection": "saves",
      "key": "key",
      "value": "{"hello": "world"}",
      "version": "*"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local save_game = json.encode({ progress = 50 })
local can_read = 1
local can_write = 1
local version = "*"   -- no version


local objects = {
  {
				collection = "saves",
				key = "savegame",
				permissionRead = can_read,
				permissionWrite = can_write,
				value = save_game,
				version = version,
  }
}

local result = client.write_storage_objects(objects)

if result.error then
  print(result.message)
  return
end

for _,ack in ipairs(result.acks) do
  pprint(ack)
end
```
{{< / code >}}

## 读取对象

就像[写入对象](#write-objects)一样，您可以从数据库服务器读取一个或多个对象。

每个对象都有一个所有者和各种权限。只有权限允许，才可以读取对象。可用 `"null"` 提取无所有者的对象，这对于所有用户都应能够读取的全局对象很实用。

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/storage" \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "object_ids": [
      {
        "collection": "saves",
        "key": "savegame",
        "user_id": "some-user-id"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const objects = await client.readStorageObjects(session, {
  "object_ids": [{
    "collection": "saves",
    "key": "savegame",
    "user_id": session.user_id
  }]
});
console.info("Read objects: %o", objects);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.ReadStorageObjectsAsync(session, new [] {
  new StorageObjectId {
    Collection = "saves",
    Key = "savegame",
    UserId = session.UserId
  }
});

Console.WriteLine("Read objects: [{0}]", string.Join(",\n  ", result.Objects));
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NStorageObjects& objects)
{
  for (auto& object : objects)
    {
        std::cout << "Object key: " << object.key << ", value: " << object.value << std::endl;
    }
};

std::vector<NReadStorageObjectId> objectIds;
NReadStorageObjectId objectId;
objectId.collection = "saves";
objectId.key = "savegame";
objectId.userId = session->getUserId();
objectIds.push_back(objectId);
client->readStorageObjects(session, objectIds, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
StorageObjectId objectId = new StorageObjectId("saves");
objectId.setKey("savegame");
objectId.setUserId(session.getUserId());
StorageObjects objects = client.readStorageObjects(session, objectId).get();
System.out.format("Read objects %s", objects.getObjectsList().toString());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var result : NakamaAPI.ApiStorageObjects = yield(client.read_storage_objects_async(session, [
    NakamaStorageObjectId.new("saves", "savegame", session.user_id)
]), "completed")

if result.is_exception():
    print("An error occurred: %s" % result)
    return

print("Read objects:")

for o in result.objects:
    print("%s" % o)
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/storage
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "object_ids": [
    {
      "collection": "saves",
      "key": "savegame",
      "user_id": "some-user-id"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local user_id = "some user id"

local objects_ids = {
  {
				collection = "saves",
				key = "savegame",
        userId = user_id
  }
}

local result = client.read_storage_objects(objects_ids)

if result.error then
  print(result.message)
  return
end

for _,object in ipairs(result.objects) do
  pprint(object)
end
```
{{< / code >}}

## 列出对象

您可以列出集合中的对象，并对结果进行分页。返回的对象可以是用户所拥有的对象的筛选器，或 `"null"`（表示并非用户所拥有的公共记录）。

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/storage/saves?user_id=some-user-id&limit=10" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const limit = 100; // default is 10.
const objects = await client.listStorageObjects(session, "saves", session.user_id, limit);
console.info("List objects: %o", objects);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const int limit = 100; // default is 10.
var result = await client.ListUsersStorageObjectsAsync(session, "saves", session.UserId, limit);
Console.WriteLine("List objects: {0}", result);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NStorageObjectListPtr list)
{
    for (auto& object : list->objects)
    {
        std::cout << "Object key: " << object.key << ", value: " << object.value << std::endl;
    }
};
client->listUsersStorageObjects(session, "saves", session->getUserId(), opt::nullopt, opt::nullopt, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
StorageObjectList objects = client.listUsersStorageObjects(session, "saves", session.getUserId()).get();
System.out.format("List objects %s", objects);
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var limit = 100 # default is 10.
var objects : NakamaAPI.ApiStorageObjectList = yield(client.list_storage_objects_async(session, "saves", session.user_id, limit), "completed")

if objects.is_exception():
  print("An error occurred: %s" % objects)
  return

print("List objects: %s" % objects)
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/storage/<collection>?user_id=<user_id>&limit=<limit>&cursor=<cursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local user_id = "some user id"
local limit = 10
local result = client.list_storage_objects("saves", user_id, limit)

if result.error then
  print(result.message)
  return
end

for _,object in ipairs(result.objects) do
  pprint(object)
end
```
{{< / code >}}

## 删除对象

如果用户有合适的权限并且他们拥有对象，则可以删除对象。

{{< code type="client" >}}
```bash
curl -X PUT "http://127.0.0.1:7350/v2/storage/delete" \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "object_ids": [
      {
        "collection": "saves",
        "key": "savegame"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
await client.deleteStorageObjects(session, {
  "object_ids": [{
    "collection": "saves",
    "key": "savegame"
  }]
});
console.info("Deleted objects.");
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.DeleteStorageObjectsAsync(session, new [] {
  new StorageObjectId {
    Collection = "saves",
    Key = "savegame"
  }
});

Console.WriteLine("Deleted objects.");
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = []()
{
    std::cout << "Deleted objects." << std::endl;
};

std::vector<NDeleteStorageObjectId> objectIds;
NDeleteStorageObjectId objectId;
objectId.collection = "saves";
objectId.key = "savegame";
objectIds.push_back(objectId);
client->deleteStorageObjects(session, objectIds, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
StorageObjectId objectId = new StorageObjectId("saves");
objectId.setKey("savegame");
client.deleteStorageObjects(session, objectId).get();
System.out.format("Deleted objects.");
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var del : NakamaAsyncResult = yield(client.delete_storage_objects_async(session, [
  NakamaStorageObjectId.new("saves", "savegame")
]), "completed")

if del.is_exception():
  print("An error occurred: %s" % del)
  return

print("Deleted objects.")
```
{{< / code >}}

{{< code type="client" >}}
```shell
PUT /v2/storage/delete
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "object_ids": [
    {
      "collection": "saves",
      "key": "savegame"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local objects_ids = {
  {
				collection = "saves",
				key = "savegame"
  }
}

local result = client.delete_storage_objects(object_ids)

if result.error then
  print(result.message)
  return
end
```
{{< / code >}}

如果对象版本与客户端发送的版本匹配，您还可以对对象进行条件删除。

{{< code type="client" >}}
```bash
curl -X PUT \
  http://127.0.0.1:7350/v2/storage/delete \
  -H 'Authorization: Bearer <session token>' \
  -d '{
    "object_ids": [
      {
        "collection": "saves",
        "key": "savegame",
        "version": "<version>"
      }
    ]
  }'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
await client.deleteStorageObjects(session, {
  "object_ids": [{
    "collection": "saves",
    "key": "savegame",
    "version": "<version>"
  }]
});
console.info("Deleted objects.");
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var result = await client.DeleteStorageObjectsAsync(session, new [] {
  new StorageObjectId {
    Collection = "saves",
    Key = "savegame",
    UserId = session.UserId,
    Version = "<version>"
  }
});

Console.WriteLine("Deleted objects.");
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = []()
{
    std::cout << "Deleted objects." << std::endl;
};

std::vector<NDeleteStorageObjectId> objectIds;
NDeleteStorageObjectId objectId;
objectId.collection = "saves";
objectId.key = "savegame";
objectId.version = "<version>";
objectIds.push_back(objectId);
client->deleteStorageObjects(session, objectIds, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
StorageObjectId objectId = new StorageObjectId("saves");
objectId.setKey("savegame");
objectId.setVersion("<version>");
client.deleteStorageObjects(session, objectId).get();
System.out.format("Deleted objects.");
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var del = yield(client.delete_storage_objects_async(session, [
  NakamaStorageObjectId.new("saves", "savegame", session.user_id, "<version>")
]), "completed")

if del.is_exception():
  print("An error occurred: %s" % del)
  return

print("Deleted objects.")
```
{{< / code >}}

{{< code type="client" >}}
```shell
PUT /v2/storage/delete
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "object_ids": [
    {
      "collection": "saves",
      "key": "savegame",
      "version": "<version>"
    }
  ]
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local objects_ids = {
  {
				collection = "saves",
				key = "savegame",
        version = "some version"
  }
}

local result = nakama.delete_storage_objects(object_ids)

if result.error then
  print(result.message)
  return
end
```
{{< / code >}}
