@dittolive/ditto 4.5.1-experimental.aarch64-linux.1.aarch64 → 4.5.2-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/DittoReactNative.podspec +25 -0
  2. package/README.md +2 -2
  3. package/node/ditto.cjs.js +1 -1
  4. package/node/ditto.cjs.js.map +1 -0
  5. package/node/ditto.cjs.pretty.js +9655 -0
  6. package/node/ditto.cjs.pretty.js.map +1 -0
  7. package/node/ditto.darwin-arm64.node +0 -0
  8. package/node/ditto.darwin-x64.node +0 -0
  9. package/node/{ditto.linux-arm64.node → ditto.linux-x64.node} +0 -0
  10. package/node/transports.darwin-arm64.node +0 -0
  11. package/node/transports.darwin-x64.node +0 -0
  12. package/package.json +2 -1
  13. package/react-native/android/CMakeLists.txt +37 -0
  14. package/react-native/android/build.gradle +186 -0
  15. package/react-native/android/cpp-adapter.cpp +254 -0
  16. package/react-native/android/gradle.properties +5 -0
  17. package/react-native/android/src/main/AndroidManifest.xml +4 -0
  18. package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKModule.java +85 -0
  19. package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKPackage.java +28 -0
  20. package/react-native/cpp/include/Arc.hpp +141 -0
  21. package/react-native/cpp/include/Attachment.h +16 -0
  22. package/react-native/cpp/include/Authentication.h +23 -0
  23. package/react-native/cpp/include/Collection.h +13 -0
  24. package/react-native/cpp/include/DQL.h +21 -0
  25. package/react-native/cpp/include/Document.h +17 -0
  26. package/react-native/cpp/include/Identity.h +17 -0
  27. package/react-native/cpp/include/Lifecycle.h +17 -0
  28. package/react-native/cpp/include/LiveQuery.h +17 -0
  29. package/react-native/cpp/include/Logger.h +22 -0
  30. package/react-native/cpp/include/Misc.h +27 -0
  31. package/react-native/cpp/include/Presence.h +14 -0
  32. package/react-native/cpp/include/SmallPeerInfo.h +19 -0
  33. package/react-native/cpp/include/Transports.h +25 -0
  34. package/react-native/cpp/include/TypedArray.hpp +167 -0
  35. package/react-native/cpp/include/Utils.h +61 -0
  36. package/react-native/cpp/include/main.h +10 -0
  37. package/react-native/cpp/src/Attachment.cpp +86 -0
  38. package/react-native/cpp/src/Authentication.cpp +227 -0
  39. package/react-native/cpp/src/Collection.cpp +54 -0
  40. package/react-native/cpp/src/DQL.cpp +256 -0
  41. package/react-native/cpp/src/Document.cpp +146 -0
  42. package/react-native/cpp/src/Identity.cpp +123 -0
  43. package/react-native/cpp/src/Lifecycle.cpp +110 -0
  44. package/react-native/cpp/src/LiveQuery.cpp +63 -0
  45. package/react-native/cpp/src/Logger.cpp +200 -0
  46. package/react-native/cpp/src/Misc.cpp +283 -0
  47. package/react-native/cpp/src/Presence.cpp +79 -0
  48. package/react-native/cpp/src/SmallPeerInfo.cpp +142 -0
  49. package/react-native/cpp/src/Transports.cpp +270 -0
  50. package/react-native/cpp/src/TypedArray.cpp +303 -0
  51. package/react-native/cpp/src/Utils.cpp +138 -0
  52. package/react-native/cpp/src/main.cpp +152 -0
  53. package/react-native/dittoffi/dittoffi.h +4700 -0
  54. package/react-native/dittoffi/ifaddrs.cpp +385 -0
  55. package/react-native/dittoffi/ifaddrs.h +206 -0
  56. package/react-native/ios/DittoRNSDK.h +7 -0
  57. package/react-native/ios/DittoRNSDK.mm +107 -0
  58. package/react-native/ios/YeetJSIUtils.h +60 -0
  59. package/react-native/ios/YeetJSIUtils.mm +196 -0
  60. package/react-native/lib/commonjs/ditto.rn.js +92 -0
  61. package/react-native/lib/commonjs/ditto.rn.js.map +1 -0
  62. package/react-native/lib/commonjs/index.js +61 -0
  63. package/react-native/lib/commonjs/index.js.map +1 -0
  64. package/react-native/lib/module/ditto.rn.js +88 -0
  65. package/react-native/lib/module/ditto.rn.js.map +1 -0
  66. package/react-native/lib/module/index.js +27 -0
  67. package/react-native/lib/module/index.js.map +1 -0
  68. package/react-native/lib/typescript/ditto.rn.d.ts +15 -0
  69. package/react-native/lib/typescript/ditto.rn.d.ts.map +1 -0
  70. package/react-native/lib/typescript/index.d.ts +1 -0
  71. package/react-native/lib/typescript/index.d.ts.map +1 -0
  72. package/react-native/src/ditto.rn.ts +91 -0
  73. package/react-native/src/environment/environment.fallback.ts +4 -0
  74. package/react-native/src/index.ts +26 -0
  75. package/react-native/src/sources/@cbor-redux.ts +2 -0
  76. package/react-native/src/sources/@ditto.core.ts +1 -0
  77. package/react-native/src/sources/@environment.ts +1 -0
  78. package/react-native/src/sources/attachment-fetch-event.ts +54 -0
  79. package/react-native/src/sources/attachment-fetcher-manager.ts +144 -0
  80. package/react-native/src/sources/attachment-fetcher.ts +134 -0
  81. package/react-native/src/sources/attachment-token.ts +48 -0
  82. package/react-native/src/sources/attachment.ts +74 -0
  83. package/react-native/src/sources/augment.ts +101 -0
  84. package/react-native/src/sources/authenticator.ts +314 -0
  85. package/react-native/src/sources/base-pending-cursor-operation.ts +239 -0
  86. package/react-native/src/sources/base-pending-id-specific-operation.ts +109 -0
  87. package/react-native/src/sources/bridge.ts +553 -0
  88. package/react-native/src/sources/build-time-constants.ts +8 -0
  89. package/react-native/src/sources/cbor.ts +35 -0
  90. package/react-native/src/sources/collection-interface.ts +67 -0
  91. package/react-native/src/sources/collection.ts +212 -0
  92. package/react-native/src/sources/collections-event.ts +99 -0
  93. package/react-native/src/sources/counter.ts +82 -0
  94. package/react-native/src/sources/ditto.ts +979 -0
  95. package/react-native/src/sources/document-id.ts +159 -0
  96. package/react-native/src/sources/document-path.ts +306 -0
  97. package/react-native/src/sources/document.ts +193 -0
  98. package/react-native/src/sources/epilogue.ts +30 -0
  99. package/react-native/src/sources/error-codes.ts +52 -0
  100. package/react-native/src/sources/error.ts +208 -0
  101. package/react-native/src/sources/essentials.ts +53 -0
  102. package/react-native/src/sources/ffi-error.ts +122 -0
  103. package/react-native/src/sources/ffi.ts +2012 -0
  104. package/react-native/src/sources/identity.ts +163 -0
  105. package/react-native/src/sources/init.ts +71 -0
  106. package/react-native/src/sources/internal.ts +109 -0
  107. package/react-native/src/sources/keep-alive.ts +73 -0
  108. package/react-native/src/sources/key-path.ts +198 -0
  109. package/react-native/src/sources/live-query-event.ts +208 -0
  110. package/react-native/src/sources/live-query-manager.ts +102 -0
  111. package/react-native/src/sources/live-query.ts +166 -0
  112. package/react-native/src/sources/logger.ts +196 -0
  113. package/react-native/src/sources/main.ts +60 -0
  114. package/react-native/src/sources/observer-manager.ts +178 -0
  115. package/react-native/src/sources/observer.ts +79 -0
  116. package/react-native/src/sources/pending-collections-operation.ts +232 -0
  117. package/react-native/src/sources/pending-cursor-operation.ts +218 -0
  118. package/react-native/src/sources/pending-id-specific-operation.ts +218 -0
  119. package/react-native/src/sources/presence-manager.ts +161 -0
  120. package/react-native/src/sources/presence.ts +233 -0
  121. package/react-native/src/sources/query-result-item.ts +116 -0
  122. package/react-native/src/sources/query-result.ts +55 -0
  123. package/react-native/src/sources/register.ts +95 -0
  124. package/react-native/src/sources/small-peer-info.ts +177 -0
  125. package/react-native/src/sources/static-tcp-client.ts +6 -0
  126. package/react-native/src/sources/store-observer.ts +177 -0
  127. package/react-native/src/sources/store.ts +385 -0
  128. package/react-native/src/sources/subscription-manager.ts +99 -0
  129. package/react-native/src/sources/subscription.ts +89 -0
  130. package/react-native/src/sources/sync-subscription.ts +90 -0
  131. package/react-native/src/sources/sync.ts +559 -0
  132. package/react-native/src/sources/test-helpers.ts +24 -0
  133. package/react-native/src/sources/transport-conditions-manager.ts +104 -0
  134. package/react-native/src/sources/transport-config.ts +430 -0
  135. package/react-native/src/sources/update-result.ts +66 -0
  136. package/react-native/src/sources/update-results-map.ts +57 -0
  137. package/react-native/src/sources/websocket-client.ts +7 -0
  138. package/react-native/src/sources/write-transaction-collection.ts +122 -0
  139. package/react-native/src/sources/write-transaction-pending-cursor-operation.ts +101 -0
  140. package/react-native/src/sources/write-transaction-pending-id-specific-operation.ts +74 -0
  141. package/react-native/src/sources/write-transaction.ts +121 -0
  142. package/react-native.config.js +9 -0
  143. package/types/ditto.d.ts.map +1 -0
  144. package/web/ditto.es6.js +1 -1
  145. package/web/ditto.es6.js.map +1 -0
  146. package/web/ditto.es6.pretty.js +12600 -0
  147. package/web/ditto.es6.pretty.js.map +1 -0
  148. package/web/ditto.umd.js +1 -1
  149. package/web/ditto.umd.js.map +1 -0
  150. package/web/ditto.umd.pretty.js +12669 -0
  151. package/web/ditto.umd.pretty.js.map +1 -0
  152. package/web/ditto.wasm +0 -0
@@ -0,0 +1,218 @@
1
+ //
2
+ // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
+ //
4
+
5
+ import * as FFI from './ffi'
6
+
7
+ import { Bridge } from './bridge'
8
+ import { Subscription } from './subscription'
9
+ import { DocumentID } from './document-id'
10
+ import { LiveQuery } from './live-query'
11
+ import { performAsyncToWorkaroundNonAsyncFFIAPI } from './internal'
12
+ import { BasePendingCursorOperation } from './base-pending-cursor-operation'
13
+
14
+ import type { QueryArguments, SortDirection } from './essentials'
15
+ import type { Document, MutableDocument } from './document'
16
+ import type { UpdateResultsMap } from './update-results-map'
17
+ import type { LiveQueryEvent } from './live-query-event'
18
+ import type { Collection } from './collection'
19
+
20
+ // -----------------------------------------------------------------------------
21
+
22
+ /**
23
+ * The closure that is called whenever the documents covered by a live query
24
+ * change.
25
+ */
26
+ export type QueryObservationHandler = (documents: Document[], event: LiveQueryEvent, signalNext?: () => void) => void | Promise<void>
27
+
28
+ // -----------------------------------------------------------------------------
29
+
30
+ /**
31
+ * These objects are returned when using `find`-like functionality on
32
+ * {@link Collection}.
33
+ *
34
+ * They allow chaining of further query-related functions to do things like add
35
+ * a limit to the number of documents you want returned or specify how you want
36
+ * the documents to be sorted and ordered.
37
+ *
38
+ * You can either call {@link exec | exec()} on the object to get an array of
39
+ * {@link Document | documents} as an immediate return value, or you can
40
+ * establish either a live query or a subscription, which both work over time.
41
+ *
42
+ * A live query, established by calling
43
+ * {@link PendingCursorOperation.observeLocal | observeLocal()}, will notify you
44
+ * every time there's an update to a document that matches the query you
45
+ * provided in the preceding `find`-like call.
46
+ *
47
+ * A subscription, established by calling
48
+ * {@link PendingCursorOperation.subscribe | subscribe()}, will act as a signal
49
+ * to other peers that the device connects to that you would like to receive
50
+ * updates from them about documents that match the query you provided in the
51
+ * preceding `find`-like call.
52
+ *
53
+ * Update and remove functionality is also exposed through this object.
54
+ */
55
+ export class PendingCursorOperation extends BasePendingCursorOperation {
56
+ sort(propertyPath: string, direction: SortDirection = 'ascending'): PendingCursorOperation {
57
+ return super.sort(propertyPath, direction) as PendingCursorOperation
58
+ }
59
+
60
+ offset(offset: number): PendingCursorOperation {
61
+ return super.offset(offset) as PendingCursorOperation
62
+ }
63
+
64
+ limit(limit: number): PendingCursorOperation {
65
+ return super.limit(limit) as PendingCursorOperation
66
+ }
67
+
68
+ async remove(): Promise<DocumentID[]> {
69
+ const ditto = this.collection.store.ditto
70
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
71
+ return ditto.deferCloseAsync(async () => {
72
+ const query = this.query
73
+ const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
74
+ const writeTransactionX = await FFI.writeTransaction(dittoHandle.deref())
75
+ const results = await FFI.collectionRemoveQueryStr(dittoHandle.deref(), this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset)
76
+ await FFI.writeTransactionCommit(dittoHandle.deref(), writeTransactionX)
77
+ return results
78
+ })
79
+
80
+ return documentsX.map((idCBOR: Uint8Array) => {
81
+ return new DocumentID(idCBOR, true)
82
+ })
83
+ })
84
+ }
85
+
86
+ async evict(): Promise<DocumentID[]> {
87
+ const ditto = this.collection.store.ditto
88
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
89
+ return ditto.deferCloseAsync(async () => {
90
+ const query = this.query
91
+ const documentsX = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
92
+ const writeTransactionX = await FFI.writeTransaction(dittoHandle.deref())
93
+ const results = await FFI.collectionEvictQueryStr(dittoHandle.deref(), this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset)
94
+ await FFI.writeTransactionCommit(dittoHandle.deref(), writeTransactionX)
95
+ return results
96
+ })
97
+
98
+ return documentsX.map((idCBOR: Uint8Array) => {
99
+ return new DocumentID(idCBOR, true)
100
+ })
101
+ })
102
+ }
103
+
104
+ async update(closure: (documents: MutableDocument[]) => void): Promise<UpdateResultsMap> {
105
+ const ditto = this.collection.store.ditto
106
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
107
+ return ditto.deferCloseAsync(async () => {
108
+ return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
109
+ const writeTransactionX = await FFI.writeTransaction(dittoHandle.deref())
110
+ const results = await super.updateWithTransaction(closure, writeTransactionX)
111
+ await FFI.writeTransactionCommit(dittoHandle.deref(), writeTransactionX)
112
+ return results
113
+ })
114
+ })
115
+ }
116
+
117
+ /**
118
+ * Enables you to subscribe to changes that occur in a collection remotely.
119
+ *
120
+ * Having a subscription acts as a signal to other peers that you are
121
+ * interested in receiving updates when local or remote changes are made to
122
+ * documents that match the query generated by the chain of operations that
123
+ * precedes the call to {@link subscribe | subscribe()}.
124
+ *
125
+ * The returned {@link Subscription} object must be kept in scope for as long
126
+ * as you want to keep receiving updates.
127
+ *
128
+ * @returns A {@link Subscription} object that must be kept in scope for as
129
+ * long as you want to keep receiving updates for documents that match the
130
+ * query specified in the preceding chain.
131
+ */
132
+ subscribe(): Subscription {
133
+ const subscription = new Subscription(this.collection as Collection, this.query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset)
134
+ this.collection.store.ditto.subscriptionManager.add(subscription)
135
+ return subscription
136
+ }
137
+
138
+ /**
139
+ * Enables you to listen for changes that occur in a collection locally.
140
+ *
141
+ * The `handler` block will be called when local changes are
142
+ * made to documents that match the query generated by the chain of operations
143
+ * that precedes the call to {@link PendingCursorOperation.observeLocal | observeLocal()}.
144
+ * The returned {@link LiveQuery} object must be kept in scope for as long as
145
+ * you want the provided `handler` to be called when an update occurs.
146
+ *
147
+ * This won't subscribe to receive changes made remotely by others and so it
148
+ * will only fire updates when a local change is made. If you want to receive
149
+ * remotely performed updates as well, you'll have to create a subscription
150
+ * via {@link PendingCursorOperation.subscribe | subscribe()} with the
151
+ * relevant query. The returned {@link LiveQuery} object must be kept in scope
152
+ * for as long as you want the provided `eventHandler` to be called when an
153
+ * update occurs.
154
+ *
155
+ * @param handler A closure that will be called every time there is a
156
+ * transaction committed to the store that involves modifications to documents
157
+ * matching the query in the collection this method was called on.
158
+ *
159
+ * @return A {@link LiveQuery} object that must be kept in scope for as long
160
+ * as you want to keep receiving updates.
161
+ */
162
+ observeLocal(handler: QueryObservationHandler): LiveQuery {
163
+ return this._observe(handler, false)
164
+ }
165
+
166
+ /**
167
+ * Enables you to listen for changes that occur in a collection locally and
168
+ * to signal when you are ready for the live query to deliver the next event.
169
+ *
170
+ * The `handler` block will be called when local changes are
171
+ * made to documents that match the query generated by the chain of operations
172
+ * that precedes the call to {@link PendingCursorOperation.observeLocalWithNextSignal | observeLocalWithNextSignal()}.
173
+ * The returned {@link LiveQuery} object must be kept in scope for as long as
174
+ * you want the provided `handler` to be called when an update occurs.
175
+ *
176
+ * This won't subscribe to receive changes made remotely by others and so it
177
+ * will only fire updates when a local change is made. If you want to receive
178
+ * remotely performed updates as well, you'll have to create a subscription
179
+ * via {@link PendingCursorOperation.subscribe | subscribe()} with the
180
+ * relevant query. The returned {@link LiveQuery} object must be kept in scope
181
+ * for as long as you want the provided `eventHandler` to be called when an
182
+ * update occurs.
183
+ *
184
+ * @param handler A closure that will be called every time there is a
185
+ * transaction committed to the store that involves modifications to
186
+ * documents matching the query in the collection that this method was called
187
+ * on.
188
+ *
189
+ * @return A {@link LiveQuery} object that must be kept in scope for as long
190
+ * as you want to keep receiving updates.
191
+ */
192
+ observeLocalWithNextSignal(handler: QueryObservationHandler): LiveQuery {
193
+ return this._observe(handler, true)
194
+ }
195
+
196
+ // ----------------------------------------------------------- Internal ------
197
+
198
+ /** @internal */
199
+ constructor(query: string, queryArgs: QueryArguments | null, collection: Collection) {
200
+ super(query, queryArgs, collection)
201
+ }
202
+
203
+ /** @internal */
204
+ _observe(handler: QueryObservationHandler, waitForNextSignal: boolean): LiveQuery {
205
+ function wrappedHandler(documents, event, nextSignal) {
206
+ try {
207
+ return handler.call(this, documents, event)
208
+ } finally {
209
+ nextSignal()
210
+ }
211
+ }
212
+
213
+ const handlerOrWrapped: QueryObservationHandler = waitForNextSignal ? handler : wrappedHandler
214
+ const liveQuery = new LiveQuery(this.query, this.queryArgs, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset, this.collection as Collection, handlerOrWrapped)
215
+ this.collection.store.ditto.liveQueryManager.startLiveQuery(liveQuery)
216
+ return liveQuery
217
+ }
218
+ }
@@ -0,0 +1,218 @@
1
+ //
2
+ // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
+ //
4
+
5
+ import * as FFI from './ffi'
6
+
7
+ import { Bridge } from './bridge'
8
+ import { Subscription } from './subscription'
9
+ import { LiveQuery } from './live-query'
10
+ import { Document, MutableDocument } from './document'
11
+ import { performAsyncToWorkaroundNonAsyncFFIAPI } from './internal'
12
+ import { BasePendingIDSpecificOperation } from './base-pending-id-specific-operation'
13
+ import { SingleDocumentLiveQueryEvent } from './live-query-event'
14
+
15
+ import type { DocumentID } from './document-id'
16
+ import type { UpdateResult } from './update-result'
17
+ import type { Collection } from './collection'
18
+
19
+
20
+ /**
21
+ * The closure that is called whenever a single documunent covered by a
22
+ * live query changes.
23
+ */
24
+ export type SingleObservationHandler = (document: Document | null, event: SingleDocumentLiveQueryEvent, signalNext?: () => void) => void | Promise<void>
25
+
26
+ // -----------------------------------------------------------------------------
27
+
28
+ /**
29
+ * These objects are returned when using {@link Collection.findByID | findByID()}
30
+ * functionality on {@link Collection | collections}.
31
+ *
32
+ * You can either call {@link exec | exec()} on the object to get an immediate
33
+ * return value, or you can establish either a live query or a subscription,
34
+ * which both work over time.
35
+ *
36
+ * A live query, established by calling
37
+ * {@link PendingIDSpecificOperation.observeLocal | observeLocal()}, will notify
38
+ * you every time there's an update to the document with the ID you provided in
39
+ * the preceding {@link Collection.findByID | findByID()} call.
40
+ *
41
+ * A subscription, established by calling {@link PendingIDSpecificOperation.subscribe | subscribe()}, will
42
+ * act as a signal to other peers that you would like to receive updates from
43
+ * them about the document with the ID you provided in the preceding
44
+ * {@link Collection.findByID | findByID()} call.
45
+ *
46
+ * Update and remove functionality is also exposed through this object.
47
+ */
48
+ export class PendingIDSpecificOperation extends BasePendingIDSpecificOperation {
49
+ async remove(): Promise<boolean> {
50
+ const ditto = this.collection.store.ditto
51
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
52
+
53
+ return ditto.deferCloseAsync(async () => {
54
+ return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
55
+ const writeTransactionX = await FFI.writeTransaction(dittoHandle.deref())
56
+ const didRemove = await FFI.collectionRemove(dittoHandle.deref(), this.collection.name, writeTransactionX, this.documentIDCBOR)
57
+ await FFI.writeTransactionCommit(dittoHandle.deref(), writeTransactionX)
58
+ return didRemove
59
+ })
60
+ })
61
+ }
62
+
63
+ async evict(): Promise<boolean> {
64
+ const ditto = this.collection.store.ditto
65
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
66
+
67
+ return ditto.deferCloseAsync(async () => {
68
+ return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
69
+ const writeTransactionX = await FFI.writeTransaction(dittoHandle.deref())
70
+ const didEvict = await FFI.collectionEvict(dittoHandle.deref(), this.collection.name, writeTransactionX, this.documentIDCBOR)
71
+ await FFI.writeTransactionCommit(dittoHandle.deref(), writeTransactionX)
72
+ return didEvict
73
+ })
74
+ })
75
+ }
76
+
77
+ async update(closure: (document: MutableDocument) => void): Promise<UpdateResult[]> {
78
+ const ditto = this.collection.store.ditto
79
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
80
+
81
+ return ditto.deferCloseAsync(async () => {
82
+ const readTransactionX = await FFI.readTransaction(dittoHandle.deref())
83
+ const documentX = await FFI.collectionGet(dittoHandle.deref(), this.collection.name, this.documentIDCBOR, readTransactionX)
84
+ FFI.readTransactionFree(readTransactionX)
85
+
86
+ if (!documentX) throw new Error(`Can't update, document with ID '${this.documentID.toString()}' not found in collection named '${this.collection.name}'`)
87
+
88
+ const mutableDocument = Bridge.mutableDocument.bridge(documentX, () => new MutableDocument())
89
+ closure(mutableDocument)
90
+
91
+ // Ownership is transferred back to the FFI layer via collectionUpdate(),
92
+ // we therefore need to explicitly unregister the instance.
93
+ Bridge.mutableDocument.unregister(mutableDocument)
94
+
95
+ const writeTransactionX = await FFI.writeTransaction(dittoHandle.deref())
96
+ await FFI.collectionUpdate(dittoHandle.deref(), this.collection.name, writeTransactionX, documentX)
97
+ await FFI.writeTransactionCommit(dittoHandle.deref(), writeTransactionX)
98
+
99
+ return mutableDocument['@ditto.updateResults'].slice()
100
+ })
101
+ }
102
+
103
+ /**
104
+ * Enables you to subscribe to changes that occur in relation to a document
105
+ * remotely.
106
+ *
107
+ * Having a subscription acts as a signal to other peers that you are
108
+ * interested in receiving updates when local or remote changes are made to
109
+ * the relevant document.
110
+ *
111
+ * The returned {@link Subscription} object must be kept in scope for as long
112
+ * as you want to keep receiving updates.
113
+ *
114
+ * @returns A {@link Subscription} object that must be kept in scope for as
115
+ * long as you want to keep receiving updates for the document.
116
+ */
117
+ subscribe(): Subscription {
118
+ const subscription = new Subscription(this.collection as Collection, this.query, null, [], -1, 0)
119
+ this.collection.store.ditto.subscriptionManager.add(subscription)
120
+ return subscription
121
+ }
122
+
123
+ /**
124
+ * Enables you to listen for changes that occur in relation to a document
125
+ * locally.
126
+ *
127
+ * This won't subscribe to receive changes made remotely by others and so it
128
+ * will only fire updates when a local change is made. If you want to receive
129
+ * remotely performed updates as well, you'll have to create a subscription
130
+ * via {@link PendingIDSpecificOperation.subscribe | subscribe()} for the same
131
+ * document ID.
132
+ *
133
+ * The returned {@link LiveQuery} object must be kept in scope for as long
134
+ * as you want the provided `handler` to be called when an update
135
+ * occurs.
136
+ *
137
+ * @param handler A block that will be called every time there is a
138
+ * transaction committed to the store that involves a modification to the
139
+ * document with the relevant ID in the collection that
140
+ * {@link PendingIDSpecificOperation.observeLocal | observeLocal()} was called on.
141
+ *
142
+ * @returns A {@link LiveQuery} object that must be kept in scope for as long
143
+ * as you want to keep receiving updates.
144
+ */
145
+ observeLocal(handler: SingleObservationHandler): LiveQuery {
146
+ return this._observe(handler, false)
147
+ }
148
+
149
+ /**
150
+ * Enables you to listen for changes that occur in relation to a document
151
+ * locally and to signal when you are ready for the live query to deliver
152
+ * the next event.
153
+ *
154
+ * This won't subscribe to receive changes made remotely by others and so it
155
+ * will only fire updates when a local change is made. If you want to receive
156
+ * remotely performed updates as well, you'll have to create a subscription
157
+ * via {@link PendingIDSpecificOperation.subscribe | subscribe()} for the same
158
+ * document ID.
159
+ *
160
+ * The returned {@link LiveQuery} object must be kept in scope for as long
161
+ * as you want the provided `handler` to be called when an update
162
+ * occurs.
163
+ *
164
+ * @param handler A block that will be called every time there is a
165
+ * transaction committed to the store that involves a modification to the
166
+ * document with the relevant ID in the collection that
167
+ * {@link PendingIDSpecificOperation.observeLocal | observeLocal()} was called on.
168
+ *
169
+ * @returns A {@link LiveQuery} object that must be kept in scope for as long
170
+ * as you want to keep receiving updates.
171
+ */
172
+ observeLocalWithNextSignal(handler: SingleObservationHandler): LiveQuery {
173
+ return this._observe(handler, true)
174
+ }
175
+
176
+ /** @internal */
177
+ constructor(documentID: DocumentID, collection: Collection) {
178
+ super(documentID, collection)
179
+ }
180
+
181
+ /** @internal */
182
+ _observe(handler: SingleObservationHandler, waitForNextSignal: boolean): LiveQuery {
183
+ const liveQuery = new LiveQuery(this.query, null, null, [], -1, 0, this.collection as Collection, (documents, event, signalNext) => {
184
+ if (documents.length > 1) {
185
+ const documentIDsAsBase64Strings = documents.map((document) => document.id.toBase64String())
186
+ throw new Error(`Internal inconsistency, single document live query returned more than one document. Query: ${this.query}, documentIDs: ${documentIDsAsBase64Strings.join(', ')}.`)
187
+ }
188
+
189
+ if (event.isInitial === false && event.oldDocuments.length > 1) throw new Error(`Internal inconsistency, single document live query returned an update event with more than one old documents. Query ${this.query}.`)
190
+ if (event.isInitial === false && event.insertions.length > 1) throw new Error(`Internal inconsistency, single document live query returned an update event with more than one insertion, which doesn't make sense for single document observations. Query ${this.query}.`)
191
+ if (event.isInitial === false && event.deletions.length > 1) throw new Error(`Internal inconsistency, single document live query returned an update event with more than one deletion, which doesn't make sense for single document observations. Query ${this.query}.`)
192
+ if (event.isInitial === false && event.updates.length > 1) throw new Error(`Internal inconsistency, single document live query returned an update event with more than one update, which doesn't make sense for single document observations. Query ${this.query}.`)
193
+ if (event.isInitial === false && event.moves.length > 0) throw new Error(`Internal inconsistency, single document live query returned an update event with moves, which doesn't make sense for single document observations. Query ${this.query}.`)
194
+
195
+ const totalNumberOfManipulations = event.isInitial === true ? 0 : event.insertions.length + event.deletions.length + event.updates.length
196
+ if (totalNumberOfManipulations > 1) throw new Error(`Internal inconsistency, single document live query returned a combination of inserts, updates, and/or deletes, which doesn't make sense for single document observation. Query ${this.query}.`)
197
+
198
+ // IDEA: use `undefined` instead of `null` and
199
+ // adapt Wasm variant plus API definition.
200
+ const document = documents[0] || null
201
+ const oldDocument = event.isInitial === true ? undefined : event.oldDocuments[0]
202
+
203
+ const singleDocumentEvent = new SingleDocumentLiveQueryEvent(event.isInitial, oldDocument)
204
+ if (waitForNextSignal) {
205
+ handler(document, singleDocumentEvent, signalNext)
206
+ } else {
207
+ try {
208
+ handler(document, singleDocumentEvent)
209
+ } finally {
210
+ signalNext()
211
+ }
212
+ }
213
+ })
214
+
215
+ this.collection.store.ditto.liveQueryManager.startLiveQuery(liveQuery)
216
+ return liveQuery
217
+ }
218
+ }
@@ -0,0 +1,161 @@
1
+ //
2
+ // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
+ //
4
+
5
+ import * as FFI from './ffi'
6
+ import { Bridge } from './bridge'
7
+ import { generateEphemeralToken } from './internal'
8
+
9
+ import type { Ditto, RemotePeer } from './ditto'
10
+
11
+ /** @internal */
12
+ export type PresenceToken = string
13
+
14
+ /**
15
+ * @internal
16
+ * @deprecated Replaced by `Presence`.
17
+ */
18
+ export class PresenceManager {
19
+ readonly ditto: Ditto
20
+
21
+ constructor(ditto: Ditto) {
22
+ this.ditto = ditto
23
+ this.isClosed = false
24
+ this.isRegistered = false
25
+ this.currentRemotePeers = []
26
+ this.callbacksByPresenceToken = {}
27
+ }
28
+
29
+ /** @internal */
30
+ addObserver(callback: (remotePeers: RemotePeer[]) => void): PresenceToken {
31
+ if (this.isClosed) {
32
+
33
+ throw new Error(`Internal inconsistency, can't add presence observer, observer mananger close()-ed.`)
34
+ }
35
+
36
+ this.registerIfNeeded()
37
+
38
+ const token = generateEphemeralToken()
39
+ this.callbacksByPresenceToken[token] = callback
40
+ this.ditto.keepAlive.retain(`PresenceObservation.${token}`)
41
+
42
+ callback(this.currentRemotePeers)
43
+
44
+ return token
45
+ }
46
+
47
+ /** @internal */
48
+ removeObserver(token: PresenceToken) {
49
+ const callback = this.callbacksByPresenceToken[token]
50
+
51
+ if (typeof callback === 'undefined') {
52
+ throw new Error(`Can't remove presence observer, token '${token}' has never been registered before.`)
53
+ }
54
+
55
+ if (callback === null) {
56
+ // Observer has already been removed, no-op.
57
+ return
58
+ }
59
+
60
+ if (typeof this.callbacksByPresenceToken[token] != 'undefined') {
61
+ this.ditto.keepAlive.release(`PresenceObservation.${token}`)
62
+
63
+ this.callbacksByPresenceToken[token] = null
64
+ this.unregisterIfNeeded()
65
+ }
66
+ }
67
+
68
+ /** @internal */
69
+ hasObserver(token: PresenceToken): boolean {
70
+ return typeof this.callbacksByPresenceToken[token] != 'undefined'
71
+ }
72
+
73
+ /** @internal */
74
+ close() {
75
+ this.isClosed = true
76
+ for (const token in this.callbacksByPresenceToken) {
77
+ this.removeObserver(token)
78
+ }
79
+ }
80
+
81
+ // ------------------------------------------------------------ Private ------
82
+
83
+ private isClosed: boolean
84
+ private isRegistered: boolean
85
+ private currentRemotePeers: RemotePeer[]
86
+ private callbacksByPresenceToken: { [key: string]: ((remotePeers: RemotePeer[]) => void) | null }
87
+
88
+ private hasObservers(): boolean {
89
+ return Object.keys(this.callbacksByPresenceToken).length > 0
90
+ }
91
+
92
+ private registerIfNeeded() {
93
+ const ditto = this.ditto
94
+ const dittoHandle = Bridge.ditto.handleFor(this.ditto)
95
+ ditto.deferClose(() => {
96
+ const needsToRegister = !this.isRegistered
97
+ if (needsToRegister) {
98
+ this.isRegistered = true
99
+ const remotePeersJSONString = FFI.dittoPresenceV1(dittoHandle.deref())
100
+ this.currentRemotePeers = this.decode(remotePeersJSONString).sort(this.compareRemotePeers)
101
+ FFI.dittoRegisterPresenceV1Callback(dittoHandle.deref(), this.handlePresenceV1Callback.bind(this))
102
+ }
103
+ })
104
+ }
105
+
106
+ private unregisterIfNeeded() {
107
+ const ditto = this.ditto
108
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
109
+ ditto.deferClose(() => {
110
+ const needsToUnregister = !this.hasObservers() && this.isRegistered
111
+ if (needsToUnregister) {
112
+ this.isRegistered = false
113
+ FFI.dittoClearPresenceCallback(dittoHandle.deref())
114
+ this.currentRemotePeers = []
115
+ }
116
+ })
117
+ }
118
+
119
+ private handlePresenceV1Callback(remotePeersJSONString: string) {
120
+ const remotePeers = this.decode(remotePeersJSONString).sort(this.compareRemotePeers)
121
+ this.currentRemotePeers = remotePeers
122
+ this.notify()
123
+ }
124
+
125
+ private notify() {
126
+ if (this.isClosed) {
127
+ // NOTE: we don't notify observers after closing.
128
+ return
129
+ }
130
+
131
+ for (const token in this.callbacksByPresenceToken) {
132
+ const callback = this.callbacksByPresenceToken[token]
133
+ if (callback) callback(this.currentRemotePeers)
134
+ }
135
+ }
136
+
137
+ private decode(remotePeersJSONString: string): RemotePeer[] {
138
+ const remotePeersJSON: any[] = JSON.parse(remotePeersJSONString)
139
+ return remotePeersJSON.map((remotePeerJSON) => {
140
+ return {
141
+ networkID: remotePeerJSON['network_id'],
142
+ deviceName: remotePeerJSON['device_name'],
143
+ rssi: remotePeerJSON['rssi'] ?? undefined,
144
+ approximateDistanceInMeters: remotePeerJSON['approximate_distance_in_meters'] ?? undefined,
145
+ connections: remotePeerJSON['connections'],
146
+ }
147
+ })
148
+ }
149
+
150
+ private compareRemotePeers(left: RemotePeer, right: RemotePeer) {
151
+ // NOTE: we use the exact same sort order here as in the ObjC version.
152
+
153
+ if (left.connections.length === 0 && right.connections.length > 0) return +1
154
+ if (left.connections.length > 0 && right.connections.length === 0) return -1
155
+
156
+ if (left.deviceName < right.deviceName) return -1
157
+ if (left.deviceName > right.deviceName) return +1
158
+
159
+ return 0
160
+ }
161
+ }