Repository: yorkie-team/yorkie
Pull request #560 (2023-06-26 08:28:00)

Add RemoveIfNotAttached options in Detach API
<!-- Thanks for sending a pull request! -->

**What this PR does / why we need it**:

If client SDK want to remove document when the document is not attached any other clients, SDK can use `RemoveIfNotAttached` option in Detach API.
```proto
message DetachDocumentRequest { 
  bytes client_id = 1; 
  string document_id = 2; 
  ChangePack change_pack = 3; 
  bool remove_if_not_attached = 4; 
}
 ```


This PR changed `yorkieServer.DetachDocument` to always use a write locker(pushpull locker)
Because this option uses the `DB.IsAttachedDocument` method, which should use the same write locker (pushpull locker) as this PR for the same reason. 
- https://github.com/yorkie-team/yorkie/pull/558

**Which issue(s) this PR fixes**: <!-- *Automatically closes linked issue when PR is merged. Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`. --> Fixes #

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**: <!-- If no, just write "NONE" in the release-note block below. If yes, a release note is required: Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required". -->
 ```release-note 
cli.Detach(client_id, document_id, change_pack, remove_if_not_attached) 
```

**Additional documentation**:

<!-- This section can be blank if this pull request does not require a release note.

Please use the following format for linking documentation: - [Usage]: <link> - [Other doc]: <link> -->

```docs

```

**Checklist**:
- [x] Added relevant tests or not required
- [x] Didn't break anything

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/yorkie-team/yorkie/pull/560?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) Report
> Merging [#560](https://app.codecov.io/gh/yorkie-team/yorkie/pull/560?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (496338c) into [main](https://app.codecov.io/gh/yorkie-team/yorkie/commit/525cacf2747f53187e41fd495e41f1b26e0a5d4e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (525cacf) will **decrease** coverage by `0.09%`.
> The diff coverage is `39.47%`.

```diff
@@            Coverage Diff             @@
##             main     #560      +/-   ##
==========================================
- Coverage   52.25%   52.17%   -0.09%     
==========================================
  Files          67       67              
  Lines        6794     6816      +22     
==========================================
+ Hits         3550     3556       +6     
- Misses       2806     2816      +10     
- Partials      438      444       +6     
```


| [Impacted Files](https://app.codecov.io/gh/yorkie-team/yorkie/pull/560?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) | Coverage Δ | |
|---|---|---|
| [client/client.go](https://app.codecov.io/gh/yorkie-team/yorkie/pull/560?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-Y2xpZW50L2NsaWVudC5nbw==) | `15.33% <0.00%> (-0.05%)` | :arrow_down: |
| [server/rpc/yorkie\_server.go](https://app.codecov.io/gh/yorkie-team/yorkie/pull/560?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL3JwYy95b3JraWVfc2VydmVyLmdv) | `53.15% <35.29%> (-0.84%)` | :arrow_down: |
| [server/backend/database/mongo/client.go](https://app.codecov.io/gh/yorkie-team/yorkie/pull/560?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2JhY2tlbmQvZGF0YWJhc2UvbW9uZ28vY2xpZW50Lmdv) | `37.59% <46.66%> (-0.19%)` | :arrow_down: |
| [server/backend/database/memory/database.go](https://app.codecov.io/gh/yorkie-team/yorkie/pull/560?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2JhY2tlbmQvZGF0YWJhc2UvbWVtb3J5L2RhdGFiYXNlLmdv) | `53.40% <100.00%> (+0.13%)` | :arrow_up: |



Patch:
diff --git a/api/yorkie/v1/yorkie.pb.go b/api/yorkie/v1/yorkie.pb.go
index 1e4a0b05..543a706b 100644
--- a/api/yorkie/v1/yorkie.pb.go
+++ b/api/yorkie/v1/yorkie.pb.go
@@ -344,6 +344,7 @@ type DetachDocumentRequest struct {
 	ClientId             []byte      `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
 	DocumentId           string      `protobuf:"bytes,2,opt,name=document_id,json=documentId,proto3" json:"document_id,omitempty"`
 	ChangePack           *ChangePack `protobuf:"bytes,3,opt,name=change_pack,json=changePack,proto3" json:"change_pack,omitempty"`
+	RemoveIfNotAttached  bool        `protobuf:"varint,4,opt,name=remove_if_not_attached,json=removeIfNotAttached,proto3" json:"remove_if_not_attached,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
 	XXX_unrecognized     []byte      `json:"-"`
 	XXX_sizecache        int32       `json:"-"`
@@ -403,6 +404,13 @@ func (m *DetachDocumentRequest) GetChangePack() *ChangePack {
 	return nil
 }
 
+func (m *DetachDocumentRequest) GetRemoveIfNotAttached() bool {
+	if m != nil {
+		return m.RemoveIfNotAttached
+	}
+	return false
+}
+
 type DetachDocumentResponse struct {
 	ClientKey            string      `protobuf:"bytes,1,opt,name=client_key,json=clientKey,proto3" json:"client_key,omitempty"`
 	ChangePack           *ChangePack `protobuf:"bytes,2,opt,name=change_pack,json=changePack,proto3" json:"change_pack,omitempty"`
@@ -1009,50 +1017,52 @@ func init() {
 func init() { proto.RegisterFile("yorkie/v1/yorkie.proto", fileDescriptor_40070c858814ab24) }
 
 var fileDescriptor_40070c858814ab24 = []byte{
-	// 674 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x56, 0xdb, 0x6e, 0xd3, 0x40,
-	0x10, 0xcd, 0xd2, 0x8b, 0x9a, 0x09, 0x0d, 0x74, 0x21, 0x69, 0x08, 0x22, 0x0d, 0xe6, 0x81, 0xa2,
-	0x4a, 0x29, 0x29, 0x52, 0x11, 0xe2, 0xa9, 0x6d, 0x90, 0x5a, 0xf1, 0x40, 0x64, 0x2e, 0x55, 0x2b,
-	0xa1, 0xc8, 0x5d, 0x8f, 0x88, 0x15, 0xd7, 0x6b, 0x7c, 0x93, 0xcc, 0x3f, 0xc0, 0x33, 0xff, 0xc0,
-	0x8f, 0xf0, 0xc8, 0x1f, 0x80, 0xc2, 0x8f, 0xa0, 0x78, 0xdd, 0xe0, 0x75, 0x96, 0x34, 0x54, 0xaa,
-	0xe0, 0xcd, 0x5e, 0xcf, 0x39, 0x73, 0x66, 0xb2, 0x33, 0x27, 0x50, 0x8d, 0xb9, 0x37, 0xb0, 0x70,
-	0x33, 0x6a, 0x6f, 0x8a, 0xa7, 0x96, 0xeb, 0xf1, 0x80, 0xd3, 0x62, 0xfa, 0x16, 0xb5, 0xeb, 0xb7,
-	0x7e, 0x87, 0x78, 0xe8, 0xf3, 0xd0, 0x63, 0xe8, 0x8b, 0x28, 0x6d, 0x1b, 0x2a, 0x3b, 0x2c, 0xb0,
-	0x22, 0x23, 0xc0, 0x3d, 0xdb, 0x42, 0x27, 0xd0, 0xf1, 0x7d, 0x88, 0x7e, 0x40, 0xef, 0x00, 0xb0,
-	0xe4, 0xa0, 0x37, 0xc0, 0xb8, 0x46, 0x9a, 0x64, 0xbd, 0xa8, 0x17, 0xc5, 0xc9, 0x73, 0x8c, 0xb5,
-	0x57, 0x50, 0xcd, 0xe3, 0x7c, 0x97, 0x3b, 0x3e, 0x9e, 0x03, 0xa4, 0xb7, 0x21, 0x7d, 0xe9, 0x59,
-	0x66, 0xed, 0x4a, 0x93, 0xac, 0x5f, 0xd5, 0x97, 0xc4, 0xc1, 0x81, 0xa9, 0x6d, 0xc3, 0x6a, 0x07,
-	0x0d, 0xa5, 0x1e, 0x09, 0x47, 0x72, 0xb8, 0xc7, 0x50, 0x9b, 0xc4, 0xa5, 0x7a, 0xa6, 0x02, 0x6d,
-	0xa8, 0xec, 0x04, 0x81, 0xc1, 0xfa, 0x1d, 0xce, 0xc2, 0xd3, 0x19, 0xd3, 0xd1, 0x6d, 0x28, 0xb1,
-	0xbe, 0xe1, 0xbc, 0xc3, 0x9e, 0x6b, 0xb0, 0x41, 0x52, 0x45, 0x69, 0xab, 0xd2, 0x1a, 0x37, 0xbc,
-	0xb5, 0x97, 0x7c, 0xed, 0x1a, 0x6c, 0xa0, 0x03, 0x1b, 0x3f, 0x6b, 0x9f, 0x08, 0x54, 0xf3, 0xe9,
-	0x66, 0x50, 0x49, 0xd7, 0xa0, 0x64, 0xa6, 0x80, 0xb3, 0xae, 0x15, 0x75, 0x38, 0x3b, 0x9a, 0x14,
-	0x34, 0x37, 0xab, 0xa0, 0x8f, 0x04, 0x2a, 0x1d, 0xfc, 0xeb, 0xfa, 0x2f, 0x4d, 0x0f, 0x87, 0x6a,
-	0x5e, 0xce, 0x6c, 0xb7, 0xea, 0xa2, 0xbf, 0xc8, 0x09, 0xdc, 0x3c, 0x34, 0x82, 0xc9, 0xf2, 0x1f,
-	0xc0, 0xa2, 0x20, 0x4f, 0x52, 0x95, 0xb6, 0x56, 0xb2, 0x54, 0xe2, 0x7e, 0xa5, 0x01, 0xe7, 0x36,
-	0x43, 0x1b, 0x12, 0xa8, 0xe4, 0x92, 0xa4, 0x45, 0x1d, 0x41, 0xd9, 0x72, 0xac, 0xc0, 0x32, 0x6c,
-	0xeb, 0x83, 0x11, 0x58, 0xdc, 0x49, 0xb3, 0x6d, 0x66, 0xb2, 0x29, 0x91, 0xad, 0x03, 0x09, 0xb6,
-	0x5f, 0xd0, 0x73, 0x44, 0x74, 0x03, 0x16, 0x30, 0x1a, 0xe9, 0x17, 0xad, 0xb8, 0x91, 0x61, 0xec,
-	0x70, 0xf6, 0x6c, 0xf4, 0x69, 0xbf, 0xa0, 0x8b, 0x98, 0xfa, 0x13, 0x28, 0xcb, 0x84, 0xf4, 0x3e,
-	0x2c, 0xb8, 0x88, 0x9e, 0x5f, 0x23, 0xcd, 0x39, 0x75, 0xf9, 0xe2, 0xfb, 0xee, 0x22, 0xcc, 0x9f,
-	0x70, 0x33, 0x4e, 0x6e, 0x92, 0x8e, 0xa7, 0x3c, 0xc2, 0xff, 0xe6, 0x26, 0xe5, 0xe5, 0x5c, 0xee,
-	0x4d, 0xfa, 0x42, 0xa0, 0xda, 0x0d, 0xfd, 0x7e, 0x37, 0xb4, 0x6d, 0x11, 0xe2, 0xff, 0xd3, 0x0e,
-	0x8c, 0xb2, 0xba, 0xa1, 0xdf, 0xef, 0x71, 0xc7, 0x8e, 0x6b, 0xf3, 0x4d, 0xb2, 0xbe, 0xa4, 0x2f,
-	0x8d, 0x0e, 0x5e, 0x38, 0x76, 0xac, 0x39, 0xb0, 0x3a, 0x21, 0x76, 0x96, 0x4d, 0x74, 0xd1, 0xee,
-	0x30, 0xa8, 0xbc, 0x76, 0x4d, 0x23, 0xc0, 0xae, 0x87, 0x3e, 0x3a, 0x0c, 0x2f, 0x63, 0xd0, 0x6a,
-	0x50, 0xcd, 0x27, 0x11, 0x35, 0x6d, 0x7d, 0x5f, 0x80, 0xe5, 0xa3, 0x84, 0xf7, 0x25, 0x7a, 0x91,
-	0xc5, 0x90, 0x1e, 0x42, 0x59, 0xf6, 0x2f, 0xda, 0xcc, 0x64, 0x56, 0x5a, 0x62, 0xfd, 0xee, 0x94,
-	0x08, 0x91, 0x48, 0x2b, 0xd0, 0xb7, 0x70, 0x3d, 0x6f, 0x45, 0x54, 0xcb, 0x4e, 0x9f, 0xda, 0xdf,
-	0xea, 0xf7, 0xa6, 0xc6, 0x8c, 0xe9, 0x0f, 0xa1, 0x2c, 0xd7, 0x28, 0xe9, 0x56, 0xf6, 0x58, 0xd2,
-	0xad, 0x6e, 0x90, 0x20, 0x96, 0xad, 0x49, 0x6e, 0x88, 0xca, 0x24, 0xe5, 0x86, 0x28, 0x7d, 0x4d,
-	0x10, 0xcb, 0x3b, 0x5d, 0x22, 0x56, 0xba, 0x8f, 0x44, 0xac, 0x36, 0x04, 0x41, 0x2c, 0x8f, 0xb8,
-	0x44, 0xac, 0x5c, 0x46, 0x12, 0xb1, 0x7a, 0x3f, 0x68, 0x05, 0x7a, 0x0c, 0xd7, 0x72, 0xc3, 0x41,
-	0xb3, 0x38, 0xf5, 0x94, 0xd7, 0xb5, 0x69, 0x21, 0x63, 0xee, 0x37, 0xb0, 0x2c, 0x6d, 0x74, 0xba,
-	0xf6, 0xe7, 0x5d, 0x2f, 0x78, 0x9b, 0xe7, 0x99, 0x81, 0x56, 0x78, 0x48, 0x76, 0x37, 0xbe, 0x0e,
-	0x1b, 0xe4, 0xdb, 0xb0, 0x41, 0x7e, 0x0c, 0x1b, 0xe4, 0xf3, 0xcf, 0x46, 0x01, 0x56, 0x4c, 0x8c,
-	0xce, 0xa0, 0x86, 0x6b, 0xb5, 0xa2, 0x76, 0x97, 0x1c, 0xcf, 0xb7, 0x9e, 0x46, 0xed, 0x93, 0xc5,
-	0xe4, 0xbf, 0xdf, 0xa3, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xbb, 0x8f, 0x9a, 0xd9, 0x3b, 0x0a,
-	0x00, 0x00,
+	// 712 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x56, 0xdd, 0x6e, 0xd3, 0x4c,
+	0x10, 0xcd, 0x7e, 0xfd, 0x51, 0x33, 0xf9, 0x1a, 0xe8, 0x16, 0xbb, 0x21, 0x15, 0x69, 0x30, 0x17,
+	0x14, 0x55, 0x4a, 0x49, 0x2b, 0x15, 0x21, 0xae, 0xda, 0x06, 0xa9, 0x15, 0x12, 0x44, 0xe6, 0xa7,
+	0x6a, 0x25, 0x64, 0xb9, 0xf6, 0x94, 0x58, 0x71, 0xbd, 0xc6, 0x76, 0x2c, 0x85, 0x77, 0x80, 0x6b,
+	0xde, 0x81, 0xb7, 0xe0, 0x8a, 0x4b, 0xde, 0x00, 0x14, 0x5e, 0x04, 0xc5, 0xeb, 0x86, 0xac, 0xb3,
+	0xa4, 0xa1, 0x52, 0x04, 0x77, 0xf6, 0xee, 0x9c, 0x33, 0x67, 0x8e, 0x77, 0x76, 0x0c, 0x6a, 0x97,
+	0x05, 0x6d, 0x07, 0x37, 0xe3, 0xfa, 0x26, 0x7f, 0xaa, 0xf9, 0x01, 0x8b, 0x18, 0xcd, 0xa7, 0x6f,
+	0x71, 0xbd, 0x7c, 0xf3, 0x57, 0x48, 0x80, 0x21, 0xeb, 0x04, 0x16, 0x86, 0x3c, 0x4a, 0xdb, 0x01,
+	0x65, 0xd7, 0x8a, 0x9c, 0xd8, 0x8c, 0x70, 0xdf, 0x75, 0xd0, 0x8b, 0x74, 0x7c, 0xdb, 0xc1, 0x30,
+	0xa2, 0xb7, 0x00, 0xac, 0x64, 0xc1, 0x68, 0x63, 0xb7, 0x44, 0xaa, 0x64, 0x3d, 0xaf, 0xe7, 0xf9,
+	0xca, 0x13, 0xec, 0x6a, 0x2f, 0x40, 0xcd, 0xe2, 0x42, 0x9f, 0x79, 0x21, 0x5e, 0x02, 0xa4, 0xab,
+	0x90, 0xbe, 0x18, 0x8e, 0x5d, 0xfa, 0xaf, 0x4a, 0xd6, 0xff, 0xd7, 0x17, 0xf8, 0xc2, 0xa1, 0xad,
+	0xed, 0xc0, 0x4a, 0x03, 0x4d, 0xa9, 0x1e, 0x01, 0x47, 0x32, 0xb8, 0x07, 0x50, 0x1a, 0xc5, 0xa5,
+	0x7a, 0xc6, 0x02, 0x5d, 0x50, 0x76, 0xa3, 0xc8, 0xb4, 0x5a, 0x0d, 0x66, 0x75, 0xce, 0x27, 0x4c,
+	0x47, 0x77, 0xa0, 0x60, 0xb5, 0x4c, 0xef, 0x0d, 0x1a, 0xbe, 0x69, 0xb5, 0x93, 0x2a, 0x0a, 0x5b,
+	0x4a, 0x6d, 0x60, 0x78, 0x6d, 0x3f, 0xd9, 0x6d, 0x9a, 0x56, 0x5b, 0x07, 0x6b, 0xf0, 0xac, 0x7d,
+	0x20, 0xa0, 0x66, 0xd3, 0x4d, 0xa0, 0x92, 0xae, 0x41, 0xc1, 0x4e, 0x01, 0x17, 0xae, 0xe5, 0x75,
+	0xb8, 0x58, 0x1a, 0x15, 0x34, 0x33, 0xa9, 0xa0, 0xcf, 0x04, 0x94, 0x06, 0xfe, 0x71, 0xfd, 0xd3,
+	0xd2, 0x43, 0xb7, 0x41, 0x0d, 0xf0, 0x9c, 0xc5, 0x68, 0x38, 0x67, 0x86, 0xc7, 0x22, 0xc3, 0x4c,
+	0xdc, 0x42, 0xbb, 0x34, 0x5b, 0x25, 0xeb, 0x0b, 0xfa, 0x32, 0xdf, 0x3d, 0x3c, 0x7b, 0xca, 0xa2,
+	0xdd, 0x74, 0x4b, 0x63, 0xa0, 0x66, 0x6b, 0x98, 0xec, 0x28, 0x5e, 0xf5, 0x33, 0x9e, 0xc2, 0x8d,
+	0x23, 0x33, 0x1a, 0xf5, 0xec, 0x1e, 0xcc, 0x73, 0xf2, 0x24, 0x55, 0x61, 0x6b, 0x69, 0x98, 0x8a,
+	0x1f, 0xca, 0x34, 0xe0, 0x52, 0x07, 0xb5, 0x1e, 0x01, 0x25, 0x93, 0x24, 0x2d, 0xea, 0x18, 0x8a,
+	0x8e, 0xe7, 0x44, 0x8e, 0xe9, 0x3a, 0xef, 0xcc, 0xc8, 0x61, 0x5e, 0x9a, 0x6d, 0x73, 0x28, 0x9b,
+	0x14, 0x59, 0x3b, 0x14, 0x60, 0x07, 0x39, 0x3d, 0x43, 0x44, 0x37, 0x60, 0x0e, 0xe3, 0xbe, 0x7e,
+	0x6e, 0xc5, 0xf2, 0x10, 0x63, 0x83, 0x59, 0x8f, 0xfb, 0x5b, 0x07, 0x39, 0x9d, 0xc7, 0x94, 0x1f,
+	0x42, 0x51, 0x24, 0xa4, 0x77, 0x61, 0xce, 0x47, 0x0c, 0xc2, 0x12, 0xa9, 0xce, 0xc8, 0xcb, 0xe7,
+	0xfb, 0x7b, 0xf3, 0x30, 0x7b, 0xca, 0xec, 0xae, 0xf6, 0x9e, 0x80, 0xa2, 0x27, 0x5f, 0xf4, 0x9f,
+	0x38, 0x7e, 0xfd, 0x93, 0x94, 0x95, 0x33, 0xdd, 0x93, 0xf4, 0x89, 0x80, 0xda, 0xec, 0x84, 0xad,
+	0x66, 0xc7, 0x75, 0x79, 0x48, 0xf8, 0x77, 0x1b, 0x70, 0x15, 0xf2, 0x7e, 0x27, 0x6c, 0x19, 0xcc,
+	0x73, 0xbb, 0x69, 0xcf, 0x2d, 0xf4, 0x17, 0x9e, 0x79, 0x6e, 0x57, 0xf3, 0x60, 0x65, 0x44, 0xec,
+	0x24, 0xd7, 0xd7, 0x55, 0xdd, 0xb1, 0x40, 0x79, 0xe9, 0xdb, 0x66, 0x84, 0xcd, 0x00, 0x43, 0xf4,
+	0x2c, 0x9c, 0x46, 0xa3, 0x95, 0x40, 0xcd, 0x26, 0xe1, 0x35, 0x6d, 0x7d, 0x9b, 0x83, 0xc5, 0xe3,
+	0x84, 0xf7, 0x39, 0x06, 0xb1, 0x63, 0x21, 0x3d, 0x82, 0xa2, 0x38, 0xf4, 0x68, 0x75, 0x28, 0xb3,
+	0x74, 0x8e, 0x96, 0x6f, 0x8f, 0x89, 0xe0, 0x89, 0xb4, 0x1c, 0x7d, 0x0d, 0xd7, 0xb3, 0xf3, 0x8b,
+	0x6a, 0xc3, 0xdd, 0x27, 0x1f, 0x8a, 0xe5, 0x3b, 0x63, 0x63, 0x06, 0xf4, 0x47, 0x50, 0x14, 0x6b,
+	0x14, 0x74, 0x4b, 0x3d, 0x16, 0x74, 0xcb, 0x0d, 0xe2, 0xc4, 0xe2, 0x3c, 0x13, 0x0d, 0x91, 0x4d,
+	0x56, 0xd1, 0x10, 0xe9, 0x30, 0xe4, 0xc4, 0xe2, 0x9d, 0x2e, 0x10, 0x4b, 0x47, 0x96, 0x40, 0x2c,
+	0x1f, 0x08, 0x9c, 0x58, 0x6c, 0x71, 0x81, 0x58, 0x7a, 0x19, 0x09, 0xc4, 0xf2, 0xfb, 0x41, 0xcb,
+	0xd1, 0x13, 0xb8, 0x96, 0x69, 0x0e, 0x3a, 0x8c, 0x93, 0x77, 0x79, 0x59, 0x1b, 0x17, 0x32, 0xe0,
+	0x7e, 0x05, 0x8b, 0xc2, 0x8d, 0x4e, 0xd7, 0x7e, 0x7f, 0xd7, 0x73, 0xde, 0xea, 0x65, 0xc3, 0x40,
+	0xcb, 0xdd, 0x27, 0x7b, 0x1b, 0x5f, 0x7a, 0x15, 0xf2, 0xb5, 0x57, 0x21, 0xdf, 0x7b, 0x15, 0xf2,
+	0xf1, 0x47, 0x25, 0x07, 0x4b, 0x36, 0xc6, 0x17, 0x50, 0xd3, 0x77, 0x6a, 0x71, 0xbd, 0x49, 0x4e,
+	0x66, 0x6b, 0x8f, 0xe2, 0xfa, 0xe9, 0x7c, 0xf2, 0xc3, 0xb8, 0xfd, 0x33, 0x00, 0x00, 0xff, 0xff,
+	0xe0, 0x36, 0xe4, 0xdd, 0x70, 0x0a, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -1681,6 +1691,16 @@ func (m *DetachDocumentRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i -= len(m.XXX_unrecognized)
 		copy(dAtA[i:], m.XXX_unrecognized)
 	}
+	if m.RemoveIfNotAttached {
+		i--
+		if m.RemoveIfNotAttached {
+			dAtA[i] = 1
+		} else {
+			dAtA[i] = 0
+		}
+		i--
+		dAtA[i] = 0x20
+	}
 	if m.ChangePack != nil {
 		{
 			size, err := m.ChangePack.MarshalToSizedBuffer(dAtA[:i])
@@ -2343,6 +2363,9 @@ func (m *DetachDocumentRequest) Size() (n int) {
 		l = m.ChangePack.Size()
 		n += 1 + l + sovYorkie(uint64(l))
 	}
+	if m.RemoveIfNotAttached {
+		n += 2
+	}
 	if m.XXX_unrecognized != nil {
 		n += len(m.XXX_unrecognized)
 	}
@@ -3350,6 +3373,26 @@ func (m *DetachDocumentRequest) Unmarshal(dAtA []byte) error {
 				return err
 			}
 			iNdEx = postIndex
+		case 4:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field RemoveIfNotAttached", wireType)
+			}
+			var v int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowYorkie
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				v |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			m.RemoveIfNotAttached = bool(v != 0)
 		default:
 			iNdEx = preIndex
 			skippy, err := skipYorkie(dAtA[iNdEx:])
diff --git a/api/yorkie/v1/yorkie.proto b/api/yorkie/v1/yorkie.proto
index efb5fec1..0acf21d9 100644
--- a/api/yorkie/v1/yorkie.proto
+++ b/api/yorkie/v1/yorkie.proto
@@ -70,6 +70,7 @@ message DetachDocumentRequest {
   bytes client_id = 1;
   string document_id = 2;
   ChangePack change_pack = 3;
+  bool remove_if_not_attached = 4;
 }
 
 message DetachDocumentResponse {
diff --git a/client/client.go b/client/client.go
index e1d25b63..d8d95234 100644
--- a/client/client.go
+++ b/client/client.go
@@ -328,7 +328,7 @@ func (c *Client) Attach(ctx context.Context, doc *document.Document) error {
 // To collect garbage things like CRDT tombstones left on the document, all the
 // changes should be applied to other replicas before GC time. For this, if the
 // document is no longer used by this client, it should be detached.
-func (c *Client) Detach(ctx context.Context, doc *document.Document) error {
+func (c *Client) Detach(ctx context.Context, doc *document.Document, removeIfNotAttached bool) error {
 	if c.status != activated {
 		return ErrClientNotActivated
 	}
@@ -346,9 +346,10 @@ func (c *Client) Detach(ctx context.Context, doc *document.Document) error {
 	res, err := c.client.DetachDocument(
 		withShardKey(ctx, c.options.APIKey, doc.Key().String()),
 		&api.DetachDocumentRequest{
-			ClientId:   c.id.Bytes(),
-			DocumentId: attachment.docID.String(),
-			ChangePack: pbChangePack,
+			ClientId:            c.id.Bytes(),
+			DocumentId:          attachment.docID.String(),
+			ChangePack:          pbChangePack,
+			RemoveIfNotAttached: removeIfNotAttached,
 		},
 	)
 	if err != nil {
diff --git a/server/backend/database/database.go b/server/backend/database/database.go
index 3c8fa097..2d4922fe 100644
--- a/server/backend/database/database.go
+++ b/server/backend/database/database.go
@@ -247,5 +247,10 @@ type Database interface {
 	) (*types.SearchResult[*DocInfo], error)
 
 	// IsDocumentAttached returns true if the document is attached to clients.
-	IsDocumentAttached(ctx context.Context, projectID types.ID, docID types.ID) (bool, error)
+	IsDocumentAttached(
+		ctx context.Context,
+		projectID types.ID,
+		docID types.ID,
+		excludeClientID types.ID,
+	) (bool, error)
 }
diff --git a/server/backend/database/memory/database.go b/server/backend/database/memory/database.go
index 8670f85e..f77bdf7d 100644
--- a/server/backend/database/memory/database.go
+++ b/server/backend/database/memory/database.go
@@ -1238,6 +1238,7 @@ func (d *DB) IsDocumentAttached(
 	ctx context.Context,
 	projectID types.ID,
 	docID types.ID,
+	excludeClientID types.ID,
 ) (bool, error) {
 	txn := d.db.Txn(false)
 	defer txn.Abort()
@@ -1252,6 +1253,9 @@ func (d *DB) IsDocumentAttached(
 
 	for raw := it.Next(); raw != nil; raw = it.Next() {
 		clientInfo := raw.(*database.ClientInfo)
+		if clientInfo.ID == excludeClientID {
+			continue
+		}
 		clientDocInfo := clientInfo.Documents[docID]
 		if clientDocInfo == nil {
 			continue
diff --git a/server/backend/database/mongo/client.go b/server/backend/database/mongo/client.go
index 25877945..5160e84f 100644
--- a/server/backend/database/mongo/client.go
+++ b/server/backend/database/mongo/client.go
@@ -1348,6 +1348,7 @@ func (c *Client) IsDocumentAttached(
 	ctx context.Context,
 	projectID types.ID,
 	docID types.ID,
+	excludeClientID types.ID,
 ) (bool, error) {
 	encodedProjectID, err := encodeID(projectID)
 	if err != nil {
@@ -1355,11 +1356,21 @@ func (c *Client) IsDocumentAttached(
 	}
 
 	clientDocInfoKey := "documents." + docID.String() + "."
-
-	result := c.collection(colClients).FindOne(ctx, bson.M{
+	filter := bson.M{
 		"project_id":                encodedProjectID,
 		clientDocInfoKey + "status": database.DocumentAttached,
-	})
+	}
+
+	if excludeClientID != "" {
+		encodedExcludeClientID, err := encodeID(excludeClientID)
+		if err != nil {
+			return false, err
+		}
+
+		filter["_id"] = bson.M{"$ne": encodedExcludeClientID}
+	}
+
+	result := c.collection(colClients).FindOne(ctx, filter)
 	if result.Err() == mongo.ErrNoDocuments {
 		return false, nil
 	}
diff --git a/server/documents/documents.go b/server/documents/documents.go
index 4142c8c3..04926799 100644
--- a/server/documents/documents.go
+++ b/server/documents/documents.go
@@ -231,7 +231,7 @@ func RemoveDocument(
 		return be.DB.UpdateDocInfoStatusToRemoved(ctx, project.ID, docID)
 	}
 
-	isAttached, err := be.DB.IsDocumentAttached(ctx, project.ID, docID)
+	isAttached, err := be.DB.IsDocumentAttached(ctx, project.ID, docID, "")
 	if err != nil {
 		return err
 	}
@@ -241,3 +241,14 @@ func RemoveDocument(
 
 	return be.DB.UpdateDocInfoStatusToRemoved(ctx, project.ID, docID)
 }
+
+// IsDocumentAttached returns true if the given document is attached to any client.
+func IsDocumentAttached(
+	ctx context.Context,
+	be *backend.Backend,
+	project *types.Project,
+	docID types.ID,
+	excludeClientID types.ID,
+) (bool, error) {
+	return be.DB.IsDocumentAttached(ctx, project.ID, docID, excludeClientID)
+}
diff --git a/server/rpc/yorkie_server.go b/server/rpc/yorkie_server.go
index 63fd45f8..ab1f46c0 100644
--- a/server/rpc/yorkie_server.go
+++ b/server/rpc/yorkie_server.go
@@ -208,21 +208,19 @@ func (s *yorkieServer) DetachDocument(
 	}
 
 	project := projects.From(ctx)
-	if pack.HasChanges() {
-		locker, err := s.backend.Coordinator.NewLocker(ctx, packs.PushPullKey(project.ID, pack.DocumentKey))
-		if err != nil {
-			return nil, err
-		}
+	locker, err := s.backend.Coordinator.NewLocker(ctx, packs.PushPullKey(project.ID, pack.DocumentKey))
+	if err != nil {
+		return nil, err
+	}
 
-		if err := locker.Lock(ctx); err != nil {
-			return nil, err
-		}
-		defer func() {
-			if err := locker.Unlock(ctx); err != nil {
-				logging.DefaultLogger().Error(err)
-			}
-		}()
+	if err := locker.Lock(ctx); err != nil {
+		return nil, err
 	}
+	defer func() {
+		if err := locker.Unlock(ctx); err != nil {
+			logging.DefaultLogger().Error(err)
+		}
+	}()
 
 	clientInfo, err := clients.FindClientInfo(ctx, s.backend.DB, project, actorID)
 	if err != nil {
@@ -233,10 +231,18 @@ func (s *yorkieServer) DetachDocument(
 		return nil, err
 	}
 
-	if err := clientInfo.DetachDocument(docInfo.ID); err != nil {
+	isAttached, err := documents.IsDocumentAttached(ctx, s.backend, project, docInfo.ID, clientInfo.ID)
+	if err != nil {
 		return nil, err
 	}
 
+	if err := clientInfo.RemoveDocument(docInfo.ID); err != nil {
+		return nil, err
+	}
+	if req.RemoveIfNotAttached && !isAttached {
+		pack.IsRemoved = true
+	}
+
 	pulled, err := packs.PushPull(ctx, s.backend, project, clientInfo, docInfo, pack, types.SyncModePushPull)
 	if err != nil {
 		return nil, err


Test patch:
diff --git a/server/backend/database/testcases/testcases.go b/server/backend/database/testcases/testcases.go
index 3d2e232b..c0714080 100644
--- a/server/backend/database/testcases/testcases.go
+++ b/server/backend/database/testcases/testcases.go
@@ -863,21 +863,21 @@ func RunIsDocumentAttachedTest(t *testing.T, db database.Database, projectID typ
 		assert.NoError(t, err)
 
 		// 01. Check if document is attached without attaching
-		attached, err := db.IsDocumentAttached(ctx, projectID, d1.ID)
+		attached, err := db.IsDocumentAttached(ctx, projectID, d1.ID, "")
 		assert.NoError(t, err)
 		assert.False(t, attached)
 
 		// 02. Check if document is attached after attaching
 		assert.NoError(t, c1.AttachDocument(d1.ID))
 		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d1))
-		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
 		assert.NoError(t, err)
 		assert.True(t, attached)
 
 		// 03. Check if document is attached after detaching
 		assert.NoError(t, c1.DetachDocument(d1.ID))
 		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d1))
-		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
 		assert.NoError(t, err)
 		assert.False(t, attached)
 
@@ -886,21 +886,21 @@ func RunIsDocumentAttachedTest(t *testing.T, db database.Database, projectID typ
 		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d1))
 		assert.NoError(t, c2.AttachDocument(d1.ID))
 		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c2, d1))
-		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
 		assert.NoError(t, err)
 		assert.True(t, attached)
 
 		// 05. Check if document is attached after a client detaching
 		assert.NoError(t, c1.DetachDocument(d1.ID))
 		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d1))
-		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
 		assert.NoError(t, err)
 		assert.True(t, attached)
 
 		// 06. Check if document is attached after another client detaching
 		assert.NoError(t, c2.DetachDocument(d1.ID))
 		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c2, d1))
-		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
 		assert.NoError(t, err)
 		assert.False(t, attached)
 	})
@@ -919,30 +919,108 @@ func RunIsDocumentAttachedTest(t *testing.T, db database.Database, projectID typ
 		// 01. Check if documents are attached after attaching
 		assert.NoError(t, c1.AttachDocument(d1.ID))
 		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d1))
-		attached, err := db.IsDocumentAttached(ctx, projectID, d1.ID)
+		attached, err := db.IsDocumentAttached(ctx, projectID, d1.ID, "")
 		assert.NoError(t, err)
 		assert.True(t, attached)
 
 		assert.NoError(t, c1.AttachDocument(d2.ID))
 		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d2))
-		attached, err = db.IsDocumentAttached(ctx, projectID, d2.ID)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d2.ID, "")
 		assert.NoError(t, err)
 		assert.True(t, attached)
 
 		// 02. Check if a document is attached after detaching another document
 		assert.NoError(t, c1.DetachDocument(d2.ID))
 		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d2))
-		attached, err = db.IsDocumentAttached(ctx, projectID, d2.ID)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d2.ID, "")
 		assert.NoError(t, err)
 		assert.False(t, attached)
-		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
 		assert.NoError(t, err)
 		assert.True(t, attached)
 
 		// 03. Check if a document is attached after detaching remaining document
 		assert.NoError(t, c1.DetachDocument(d1.ID))
 		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d1))
-		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
+		assert.NoError(t, err)
+		assert.False(t, attached)
+	})
+
+	t.Run("IsDocumentAttached exclude client info test", func(t *testing.T) {
+		ctx := context.Background()
+
+		// 00. Create two clients and a document
+		c1, err := db.ActivateClient(ctx, projectID, t.Name()+"1")
+		assert.NoError(t, err)
+		c2, err := db.ActivateClient(ctx, projectID, t.Name()+"2")
+		assert.NoError(t, err)
+		d1, err := db.FindDocInfoByKeyAndOwner(ctx, projectID, c1.ID, helper.TestDocKey(t), true)
+		assert.NoError(t, err)
+
+		// 01. Check if document is attached without attaching
+		attached, err := db.IsDocumentAttached(ctx, projectID, d1.ID, "")
+		assert.NoError(t, err)
+		assert.False(t, attached)
+
+		// 02. Check if document is attached after attaching
+		assert.NoError(t, c1.AttachDocument(d1.ID))
+		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d1))
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
+		assert.NoError(t, err)
+		assert.True(t, attached)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, c1.ID)
+		assert.NoError(t, err)
+		assert.False(t, attached)
+
+		// 03. Check if document is attached after detaching
+		assert.NoError(t, c1.DetachDocument(d1.ID))
+		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d1))
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
+		assert.NoError(t, err)
+		assert.False(t, attached)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, c1.ID)
+		assert.NoError(t, err)
+		assert.False(t, attached)
+
+		// 04. Check if document is attached after two clients attaching
+		assert.NoError(t, c1.AttachDocument(d1.ID))
+		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d1))
+		assert.NoError(t, c2.AttachDocument(d1.ID))
+		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c2, d1))
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
+		assert.NoError(t, err)
+		assert.True(t, attached)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, c1.ID)
+		assert.NoError(t, err)
+		assert.True(t, attached)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, c2.ID)
+		assert.NoError(t, err)
+		assert.True(t, attached)
+
+		// 05. Check if document is attached after a client detaching
+		assert.NoError(t, c1.DetachDocument(d1.ID))
+		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c1, d1))
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
+		assert.NoError(t, err)
+		assert.True(t, attached)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, c1.ID)
+		assert.NoError(t, err)
+		assert.True(t, attached)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, c2.ID)
+		assert.NoError(t, err)
+		assert.False(t, attached)
+
+		// 06. Check if document is attached after another client detaching
+		assert.NoError(t, c2.DetachDocument(d1.ID))
+		assert.NoError(t, db.UpdateClientInfoAfterPushPull(ctx, c2, d1))
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, "")
+		assert.NoError(t, err)
+		assert.False(t, attached)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, c1.ID)
+		assert.NoError(t, err)
+		assert.False(t, attached)
+		attached, err = db.IsDocumentAttached(ctx, projectID, d1.ID, c2.ID)
 		assert.NoError(t, err)
 		assert.False(t, attached)
 	})
diff --git a/test/integration/admin_test.go b/test/integration/admin_test.go
index 69a24656..695e783e 100644
--- a/test/integration/admin_test.go
+++ b/test/integration/admin_test.go
@@ -143,7 +143,7 @@ func TestAdmin(t *testing.T) {
 		assert.Equal(t, document.StatusAttached, doc.Status())
 
 		// 03. remove document that is detached by the client.
-		assert.NoError(t, cli.Detach(ctx, doc))
+		assert.NoError(t, cli.Detach(ctx, doc, false))
 		err = adminCli.RemoveDocument(ctx, "default", doc.Key().String(), false)
 		assert.NoError(t, err)
 		assert.Equal(t, document.StatusDetached, doc.Status())
diff --git a/test/integration/document_test.go b/test/integration/document_test.go
index fd834354..dc7a8730 100644
--- a/test/integration/document_test.go
+++ b/test/integration/document_test.go
@@ -52,7 +52,7 @@ func TestDocument(t *testing.T) {
 		assert.NoError(t, err)
 		assert.True(t, doc.IsAttached())
 
-		err = c1.Detach(ctx, doc)
+		err = c1.Detach(ctx, doc, false)
 		assert.NoError(t, err)
 		assert.False(t, doc.IsAttached())
 
@@ -72,6 +72,38 @@ func TestDocument(t *testing.T) {
 		assert.Error(t, err)
 	})
 
+	t.Run("detach removeIfNotAttached flag test", func(t *testing.T) {
+		// 01. create a document and attach it to c1
+		ctx := context.Background()
+		doc := document.New(helper.TestDocKey(t))
+		err := doc.Update(func(root *json.Object) error {
+			root.SetString("k1", "v1")
+			return nil
+		}, "update k1 with v1")
+		assert.NoError(t, err)
+
+		err = c1.Attach(ctx, doc)
+		assert.NoError(t, err)
+		assert.True(t, doc.IsAttached())
+
+		// 02. detach with removeIfNotAttached option false
+		err = c1.Detach(ctx, doc, false)
+		assert.NoError(t, err)
+		assert.False(t, doc.IsAttached())
+		assert.Equal(t, doc.Status(), document.StatusDetached)
+
+		// 03. attach again to c1 and check if it is attached normally
+		err = c1.Attach(ctx, doc)
+		assert.NoError(t, err)
+		assert.True(t, doc.IsAttached())
+
+		// 04. detach with removeIfNotAttached option true
+		err = c1.Detach(ctx, doc, true)
+		assert.NoError(t, err)
+		assert.False(t, doc.IsAttached())
+		assert.Equal(t, doc.Status(), document.StatusRemoved)
+	})
+
 	t.Run("concurrent complex test", func(t *testing.T) {
 		ctx := context.Background()
 		d1 := document.New(helper.TestDocKey(t))
@@ -305,7 +337,7 @@ func TestDocument(t *testing.T) {
 
 		// 02. cli1 removes d1 and cli2 detaches d2.
 		assert.NoError(t, c1.Remove(ctx, d1))
-		assert.NoError(t, c2.Detach(ctx, d2))
+		assert.NoError(t, c2.Detach(ctx, d2, false))
 		assert.Equal(t, d1.Status(), document.StatusRemoved)
 		assert.Equal(t, d2.Status(), document.StatusRemoved)
 	})
@@ -350,7 +382,7 @@ func TestDocument(t *testing.T) {
 
 		// 01. abnormal behavior on detached state
 		d1 := document.New(helper.TestDocKey(t))
-		assert.ErrorIs(t, cli.Detach(ctx, d1), client.ErrDocumentNotAttached)
+		assert.ErrorIs(t, cli.Detach(ctx, d1, false), client.ErrDocumentNotAttached)
 		assert.ErrorIs(t, cli.Sync(ctx, client.WithDocKey(d1.Key())), client.ErrDocumentNotAttached)
 		assert.ErrorIs(t, cli.Remove(ctx, d1), client.ErrDocumentNotAttached)
 
@@ -362,7 +394,7 @@ func TestDocument(t *testing.T) {
 		assert.NoError(t, cli.Remove(ctx, d1))
 		assert.ErrorIs(t, cli.Remove(ctx, d1), client.ErrDocumentNotAttached)
 		assert.ErrorIs(t, cli.Sync(ctx, client.WithDocKey(d1.Key())), client.ErrDocumentNotAttached)
-		assert.ErrorIs(t, cli.Detach(ctx, d1), client.ErrDocumentNotAttached)
+		assert.ErrorIs(t, cli.Detach(ctx, d1, false), client.ErrDocumentNotAttached)
 	})
 
 	t.Run("removed document removal with watching test", func(t *testing.T) {
diff --git a/test/integration/gc_test.go b/test/integration/gc_test.go
index 58d7ae06..d425f2b2 100644
--- a/test/integration/gc_test.go
+++ b/test/integration/gc_test.go
@@ -366,7 +366,7 @@ func TestGarbageCollection(t *testing.T) {
 		assert.Equal(t, 6, d1.GarbageLen())
 		assert.Equal(t, 0, d2.GarbageLen())
 
-		err = c2.Detach(ctx, d2)
+		err = c2.Detach(ctx, d2, false)
 		assert.NoError(t, err)
 
 		// (2, 1) -> (2, 2): syncedseqs:(1, x)
diff --git a/test/integration/history_test.go b/test/integration/history_test.go
index 0142c06f..b03446da 100644
--- a/test/integration/history_test.go
+++ b/test/integration/history_test.go
@@ -41,7 +41,7 @@ func TestHistory(t *testing.T) {
 		ctx := context.Background()
 		d1 := document.New(helper.TestDocKey(t))
 		assert.NoError(t, cli.Attach(ctx, d1))
-		defer func() { assert.NoError(t, cli.Detach(ctx, d1)) }()
+		defer func() { assert.NoError(t, cli.Detach(ctx, d1, false)) }()
 
 		assert.NoError(t, d1.Update(func(root *json.Object) error {
 			root.SetNewArray("todos")
diff --git a/test/integration/peer_awareness_test.go b/test/integration/peer_awareness_test.go
index 65cd882d..316d52e2 100644
--- a/test/integration/peer_awareness_test.go
+++ b/test/integration/peer_awareness_test.go
@@ -43,9 +43,9 @@ func TestPeerAwareness(t *testing.T) {
 		d1 := document.New(helper.TestDocKey(t))
 		d2 := document.New(helper.TestDocKey(t))
 		assert.NoError(t, c1.Attach(ctx, d1))
-		defer func() { assert.NoError(t, c1.Detach(ctx, d1)) }()
+		defer func() { assert.NoError(t, c1.Detach(ctx, d1, false)) }()
 		assert.NoError(t, c2.Attach(ctx, d2))
-		defer func() { assert.NoError(t, c2.Detach(ctx, d2)) }()
+		defer func() { assert.NoError(t, c2.Detach(ctx, d2, false)) }()
 
 		var expected []watchResponsePair
 		var responsePairs []watchResponsePair
@@ -130,7 +130,7 @@ func TestPeerAwareness(t *testing.T) {
 		d2 := document.New(helper.TestDocKey(t))
 		d3 := document.New(key.Key(helper.TestDocKey(t) + "2"))
 		assert.NoError(t, c1.Attach(ctx, d1))
-		defer func() { assert.NoError(t, c1.Detach(ctx, d1)) }()
+		defer func() { assert.NoError(t, c1.Detach(ctx, d1, false)) }()
 
 		var expected []watchResponsePair
 		var responsePairs []watchResponsePair
@@ -181,9 +181,9 @@ func TestPeerAwareness(t *testing.T) {
 			},
 		})
 		assert.NoError(t, c2.Attach(ctx, d2))
-		defer func() { assert.NoError(t, c2.Detach(ctx, d2)) }()
+		defer func() { assert.NoError(t, c2.Detach(ctx, d2, false)) }()
 		assert.NoError(t, c2.Attach(ctx, d3))
-		defer func() { assert.NoError(t, c2.Detach(ctx, d3)) }()
+		defer func() { assert.NoError(t, c2.Detach(ctx, d3, false)) }()
 
 		watch2Ctx, cancel2 := context.WithCancel(ctx)
 		_, err = c2.Watch(watch2Ctx, d2)
diff --git a/test/integration/retention_test.go b/test/integration/retention_test.go
index 566d6d38..2f33cd3f 100644
--- a/test/integration/retention_test.go
+++ b/test/integration/retention_test.go
@@ -85,7 +85,7 @@ func TestRetention(t *testing.T) {
 
 		doc := document.New(helper.TestDocKey(t))
 		assert.NoError(t, cli.Attach(ctx, doc))
-		defer func() { assert.NoError(t, cli.Detach(ctx, doc)) }()
+		defer func() { assert.NoError(t, cli.Detach(ctx, doc, false)) }()
 
 		assert.NoError(t, doc.Update(func(root *json.Object) error {
 			root.SetNewArray("todos")
@@ -147,7 +147,7 @@ func TestRetention(t *testing.T) {
 		ctx := context.Background()
 		d1 := document.New(helper.TestDocKey(t))
 		assert.NoError(t, cli1.Attach(ctx, d1))
-		defer func() { assert.NoError(t, cli1.Detach(ctx, d1)) }()
+		defer func() { assert.NoError(t, cli1.Detach(ctx, d1, false)) }()
 
 		err = d1.Update(func(root *json.Object) error {
 			root.SetNewText("k1")
@@ -222,7 +222,7 @@ func TestRetention(t *testing.T) {
 
 		d2 := document.New(helper.TestDocKey(t))
 		assert.NoError(t, cli2.Attach(ctx, d2))
-		defer func() { assert.NoError(t, cli2.Detach(ctx, d2)) }()
+		defer func() { assert.NoError(t, cli2.Detach(ctx, d2, false)) }()
 
 		// Create 6 changes
 		for _, edit := range edits {