@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,101 @@
1
+ //
2
+ // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
+ //
4
+
5
+ // NOTE: proxy was originally written in pure JS rather than TypeScript. We'll
6
+ // gradually port it to TypeScript and until done, we've renamed the pure JS
7
+ // file to proxy.raw.js and introduced proxy.ts that'll contain all parts ported
8
+ // to TypeScript or type declarations for the JS parts.
9
+
10
+ // import * as proxyRaw from './proxy.raw'
11
+
12
+ import * as FFI from './ffi'
13
+ import { DocumentID } from './document-id'
14
+ import { AttachmentToken } from './attachment-token'
15
+ import { Attachment } from './attachment'
16
+ import { Counter } from './counter'
17
+ import { Register } from './register'
18
+
19
+ // Takes an annotated JSON representation of a document (see CRDT's Document
20
+ // class for more info about what an annotated representation is) and turns it
21
+ // into a JavaScript-appropriate version. This means that counters get
22
+ // represented by `Counter` objects and attachments get represented by
23
+ // `Attachment` objects.
24
+ export function augmentJSONValue(json, mutDoc, workingPath) {
25
+ if (json && typeof json === 'object') {
26
+ if (Array.isArray(json)) {
27
+ return json.map((v, idx) => augmentJSONValue(v, mutDoc, `${workingPath}[${idx}]`))
28
+ } else if (json[FFI.DittoCRDTTypeKey] === FFI.DittoCRDTType.counter) {
29
+ return Counter['@ditto.create'](mutDoc, workingPath, json[FFI.DittoCRDTValueKey])
30
+ } else if (json[FFI.DittoCRDTTypeKey] === FFI.DittoCRDTType.register) {
31
+ return Register['@ditto.create'](mutDoc, workingPath, json[FFI.DittoCRDTValueKey])
32
+ } else if (json[FFI.DittoCRDTTypeKey] === FFI.DittoCRDTType.attachment) {
33
+ return new AttachmentToken(json)
34
+ } else {
35
+ for (const [key, value] of Object.entries(json)) {
36
+ json[key] = augmentJSONValue(value, mutDoc, `${workingPath}['${key}']`)
37
+ }
38
+ return json
39
+ }
40
+ } else {
41
+ return json
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Converts objects that may contain instances of classes of this SDK, i.e.
47
+ * `DocumentID`, `Counter`, `Register` and `Attachment`, into plain JS objects
48
+ * that can be passed to the FFI layer.
49
+ */
50
+ export function desugarJSObject(jsObj: any, atRoot: boolean = false): any {
51
+ if (jsObj && typeof jsObj === 'object') {
52
+ if (Array.isArray(jsObj)) {
53
+ return jsObj.map((v, idx) => desugarJSObject(v, false))
54
+ } else if (jsObj instanceof DocumentID) {
55
+ return jsObj.value
56
+ } else if (jsObj instanceof Counter) {
57
+ const counterJSON = {}
58
+ counterJSON[FFI.DittoCRDTTypeKey] = FFI.DittoCRDTType.counter
59
+ counterJSON[FFI.DittoCRDTValueKey] = jsObj.value
60
+ return counterJSON
61
+ } else if (jsObj instanceof Register) {
62
+ const registerJSON = {}
63
+ registerJSON[FFI.DittoCRDTTypeKey] = FFI.DittoCRDTType.register
64
+ registerJSON[FFI.DittoCRDTValueKey] = jsObj.value
65
+ return registerJSON
66
+ } else if (jsObj instanceof Attachment) {
67
+ const attachmentJSON = {
68
+ _id: jsObj.token.id,
69
+ _len: jsObj.token.len,
70
+ _meta: jsObj.token.metadata,
71
+ }
72
+ attachmentJSON[FFI.DittoCRDTTypeKey] = FFI.DittoCRDTType.attachment
73
+ return attachmentJSON
74
+ } else {
75
+ for (const [key, value] of Object.entries(jsObj)) {
76
+ jsObj[key] = desugarJSObject(value, false)
77
+ }
78
+ return jsObj
79
+ }
80
+ } else {
81
+ checkForUnsupportedValues(jsObj)
82
+ return jsObj
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Throws an error if input is a non-finite float value.
88
+ *
89
+ * Workaround while we don't have a reliable way of receiving error messages
90
+ * from `dittoCore.ditto_collection_insert_value()`.
91
+ *
92
+ * See https://github.com/getditto/ditto/issues/8657 for details.
93
+ *
94
+ * @param jsObj The object to check.
95
+ * @throws {Error} If `jsObj` is a non-finite float value.
96
+ */
97
+ function checkForUnsupportedValues(jsObj: any): void {
98
+ if (Number.isNaN(jsObj) || jsObj === Infinity || jsObj === -Infinity) {
99
+ throw new Error('Non-finite float values are not supported')
100
+ }
101
+ }
@@ -0,0 +1,314 @@
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 { Logger } from './logger'
9
+ import { Observer } from './observer'
10
+ import { ObserverManager } from './observer-manager'
11
+
12
+ import type { Ditto } from './ditto'
13
+ import type { KeepAlive } from './keep-alive'
14
+
15
+
16
+ // -----------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Provides info about the authentication status.
20
+ */
21
+ export type AuthenticationStatus = {
22
+ /**
23
+ * Returns `true` if authenticated, otherwise returns `false`.
24
+ */
25
+ isAuthenticated: boolean
26
+
27
+ /**
28
+ * If authenticated, returns the `userID` if one was provided by the
29
+ * authentication service. Otherwise returns `null`.
30
+ */
31
+ userID: string | null
32
+ }
33
+
34
+ /**
35
+ * Provides feedback to the developer about Ditto authentication status.
36
+ */
37
+ export interface AuthenticationHandler {
38
+ /**
39
+ * There is no Ditto authentication token or it has expired. Sync will not
40
+ * work until there is a successful login using one of the login methods on
41
+ * {@link Authenticator}.
42
+ */
43
+ authenticationRequired: (authenticator: Authenticator) => void
44
+
45
+ /**
46
+ * Warns that the Ditto authentication token is getting old.
47
+ *
48
+ * Ditto will attempt to refresh tokens periodically, starting from halfway
49
+ * through the token's validity period. This reduces the risk of
50
+ * authentication expiring while the user is offline.
51
+ *
52
+ * The refresh will happen automatically if Ditto has a suitable refresh
53
+ * token. If new credentials are required, such as a third-party token or a
54
+ * username/password, then Ditto does not cache that information and you must
55
+ * submit it again using one of the `login` methods on {@link Authenticator}.
56
+ */
57
+ authenticationExpiringSoon: (authenticator: Authenticator, secondsRemaining: number) => void
58
+
59
+ /**
60
+ * Notifies the authentication handler that the authentication status did
61
+ * change. Use the `authenticator`s property `status` to query for the current
62
+ * authentication status.
63
+ *
64
+ * This method is **optional**.
65
+ */
66
+ authenticationStatusDidChange?: (authenticator: Authenticator) => void
67
+ }
68
+
69
+ // -----------------------------------------------------------------------------
70
+
71
+ /**
72
+ * Log in to a remote authentication service, using an
73
+ * `OnlineWithAuthentication` or an `Online` identity.
74
+ */
75
+ export class Authenticator {
76
+ /**
77
+ * Returns `true` if authentication is available and the login methods can be
78
+ * used, otherwise returns `false`. Currently, authentication is only
79
+ * available if Ditto was initialized with an identity of type
80
+ * {@link IdentityOnlineWithAuthentication | 'onlineWithAuthentication'}.
81
+ */
82
+ readonly loginSupported: boolean
83
+
84
+ /**
85
+ Returns the current authentication status.
86
+ */
87
+ get status(): AuthenticationStatus {
88
+ return this._status
89
+ }
90
+
91
+ /**
92
+ * Log in to Ditto with a third-party token. Throws if authentication is not
93
+ * available, which can be checked with {@link loginSupported}.
94
+ *
95
+ * @param token the authentication token required to log in.
96
+ * @param provider the name of the authentication provider.
97
+ */
98
+ loginWithToken(token: string, provider: string): Promise<void> {
99
+ throw new Error(`Authenticator.loginWithToken() is abstract and must be implemented by subclasses.`)
100
+ }
101
+
102
+ /**
103
+ * Log in to Ditto with a username and password. Throws if authentication is
104
+ * not available, which can be checked with {@link loginSupported}.
105
+ *
106
+ * @param username the username component of the credentials used for log in.
107
+ * @param password the password component of the credentials used for log in.
108
+ * @param provider the name of the authentication provider.
109
+ */
110
+ loginWithUsernameAndPassword(username: string, password: string, provider: string): Promise<void> {
111
+ throw new Error(`Authenticator.loginWithUsernameAndPassword() is abstract and must be implemented by subclasses.`)
112
+ }
113
+
114
+ /**
115
+ * Log out of Ditto.
116
+ *
117
+ * This will stop sync, shut down all replication sessions, and remove any
118
+ * cached authentication credentials. Note that this does not remove any data
119
+ * from the store. If you wish to delete data from the store then use the
120
+ * optional `cleanupFn` parameter to perform any required cleanup.
121
+ *
122
+ * @param cleanupFn An optional function that will be called with the relevant
123
+ * [Ditto] instance as the sole argument that allows you to perform any
124
+ * required cleanup of the store as part of the logout process.
125
+ */
126
+ logout(cleanupFn?: (ditto: Ditto) => void): Promise<void> {
127
+ throw new Error(`Authenticator.logout() is abstract and must be implemented by subclasses.`)
128
+ }
129
+
130
+ /*
131
+ * Registers a callback that is called whenever the authentication status
132
+ * changes. Returns an `Observer` object that needs to be retained as long as
133
+ * you want to receive the updates.
134
+ */
135
+ observeStatus(callback: (authenticationStatus: AuthenticationStatus) => void): Observer {
136
+ const token = this.observerManager.addObserver(callback)
137
+ return new Observer(this.observerManager, token, { stopsWhenFinalized: true })
138
+ }
139
+
140
+ /** @internal */
141
+ constructor(keepAlive: KeepAlive) {
142
+ this.keepAlive = keepAlive
143
+ this._status = { isAuthenticated: false, userID: null }
144
+ this.loginSupported = false
145
+ this.observerManager = new ObserverManager('AuthenticationStatusObservation', { keepAlive })
146
+ }
147
+
148
+ /** @internal */
149
+ '@ditto.authenticationExpiring'(number): void {
150
+ throw new Error(`Authenticator['@ditto.authenticationExpiring']() is abstract and must be implemented by subclasses.`)
151
+ }
152
+
153
+ /** @internal */
154
+ '@ditto.authClientValidityChanged'(isWebValid: boolean, isX509Valid: boolean): void {
155
+ throw new Error(`Authenticator['@ditto.authClientValidityChanged']() is abstract and must be implemented by subclasses.`)
156
+ }
157
+
158
+ /** @internal */
159
+ close() {
160
+ this.observerManager.close()
161
+ }
162
+
163
+ /** @internal */
164
+ protected keepAlive: KeepAlive
165
+
166
+ /** @internal */
167
+ protected observerManager: ObserverManager
168
+
169
+ /** @internal */
170
+ protected _status: AuthenticationStatus
171
+ }
172
+
173
+ // -----------------------------------------------------------------------------
174
+
175
+ /** @internal */
176
+ export class OnlineAuthenticator extends Authenticator {
177
+ readonly loginSupported: boolean = true
178
+
179
+ async loginWithToken(token: string, provider: string): Promise<void> {
180
+ const ditto = this.ditto.deref()
181
+ if (!ditto) {
182
+ return
183
+ }
184
+
185
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
186
+ const dittoPointer = dittoHandle.derefOrNull()
187
+ if (!dittoPointer) {
188
+ return
189
+ }
190
+
191
+ return ditto.deferCloseAsync(async () => {
192
+ await FFI.dittoAuthClientLoginWithToken(dittoPointer, token, provider)
193
+ })
194
+ }
195
+
196
+ async loginWithUsernameAndPassword(username: string, password: string, provider: string): Promise<void> {
197
+ const ditto = this.ditto.deref()
198
+ if (!ditto) {
199
+ return
200
+ }
201
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
202
+ const dittoPointer = dittoHandle.derefOrNull()
203
+
204
+ if (!dittoPointer) {
205
+ return
206
+ }
207
+
208
+ return ditto.deferCloseAsync(async () => {
209
+ await FFI.dittoAuthClientLoginWithUsernameAndPassword(dittoPointer, username, password, provider)
210
+ })
211
+ }
212
+
213
+ async logout(cleanupFn?: (ditto: Ditto) => void): Promise<void> {
214
+ const ditto = this.ditto.deref()
215
+ if (!ditto) {
216
+ return
217
+ }
218
+
219
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
220
+ const dittoPointer = dittoHandle.derefOrNull()
221
+
222
+ if (!dittoPointer) {
223
+ return
224
+ }
225
+
226
+ return ditto.deferCloseAsync(async () => {
227
+ await FFI.dittoAuthClientLogout(dittoPointer)
228
+ ditto.stopSync()
229
+ cleanupFn?.(ditto)
230
+ })
231
+ }
232
+
233
+ readonly authenticationHandler: AuthenticationHandler
234
+
235
+ private ditto: WeakRef<Ditto>
236
+
237
+ constructor(keepAlive: KeepAlive, ditto: Ditto, authenticationHandler: AuthenticationHandler) {
238
+ super(keepAlive)
239
+ this._status = { isAuthenticated: false, userID: null }
240
+ this.ditto = new WeakRef<Ditto>(ditto)
241
+ this.authenticationHandler = authenticationHandler
242
+ this.updateAndNotify(false)
243
+ }
244
+
245
+ '@ditto.authenticationExpiring'(secondsRemaining: number): void {
246
+ const authenticationHandler = this.authenticationHandler
247
+ if (secondsRemaining > 0) {
248
+ authenticationHandler.authenticationExpiringSoon(this, secondsRemaining)
249
+ } else {
250
+ authenticationHandler.authenticationRequired(this)
251
+ }
252
+ }
253
+
254
+ '@ditto.authClientValidityChanged'(isWebValid: boolean, isX509Valid: boolean): void {
255
+ this.updateAndNotify(true)
256
+ }
257
+
258
+ private updateAndNotify(shouldNotify: boolean) {
259
+ const ditto = this.ditto.deref()
260
+ if (!ditto) {
261
+ Logger.debug('Unable to update auth status and notify, related Ditto object does not exist anymore.')
262
+ return
263
+ }
264
+
265
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
266
+ const dittoPointer = dittoHandle.derefOrNull()
267
+ if (!dittoPointer) {
268
+ Logger.debug('Unable to update auth status and notify, related Ditto object does not exist anymore.')
269
+ return
270
+ }
271
+
272
+ const wasAuthenticated = this.status.isAuthenticated
273
+ const previousUserID = this.status.userID
274
+
275
+ const isAuthenticated = FFI.dittoAuthClientIsWebValid(dittoPointer)
276
+ const userID = FFI.dittoAuthClientUserID(dittoPointer)
277
+ const status = { isAuthenticated, userID }
278
+
279
+ this._status = status
280
+
281
+ if (shouldNotify) {
282
+ const sameStatus = !!wasAuthenticated === !!isAuthenticated && previousUserID === userID
283
+ if (!sameStatus) {
284
+ this.authenticationHandler.authenticationStatusDidChange?.call(this.authenticationHandler, this)
285
+ this.observerManager.notify(status)
286
+ }
287
+ }
288
+ }
289
+ }
290
+
291
+ // -----------------------------------------------------------------------------
292
+
293
+ /** @internal */
294
+ export class NotAvailableAuthenticator extends Authenticator {
295
+ async loginWithToken(token: string, provider: string): Promise<never> {
296
+ throw new Error(`Can't login, authentication is not supported for the identity in use, please use an onlineWithAuthentication identity.`)
297
+ }
298
+
299
+ async loginWithUsernameAndPassword(username: string, password: string, provider: string): Promise<never> {
300
+ throw new Error(`Can't login, authentication is not supported for the identity in use, please use an onlineWithAuthentication identity.`)
301
+ }
302
+
303
+ logout(cleanupFn?: (ditto: Ditto) => void): Promise<never> {
304
+ throw new Error(`Can't logout, authentication is not supported for the identity in use, please use an onlineWithAuthentication identity.`)
305
+ }
306
+
307
+ '@ditto.authenticationExpiring'(secondsRemaining: number): never {
308
+ throw new Error(`Internal inconsistency, authentication is not available, yet the @ditto.authenticationExpiring() was called on authenticator: ${this}`)
309
+ }
310
+
311
+ '@ditto.authClientValidityChanged'(isWebValid: boolean, isX509Valid: boolean): never {
312
+ throw new Error(`Internal inconsistency, authentication is not available, yet the @ditto.authClientValidityChanged() was called on authenticator: ${this}`)
313
+ }
314
+ }
@@ -0,0 +1,239 @@
1
+ //
2
+ // Copyright © 2023 DittoLive Incorporated. All rights reserved.
3
+ //
4
+
5
+ import * as FFI from './ffi'
6
+
7
+ import { Document, MutableDocument } from './document'
8
+ import { UpdateResultsMap } from './update-results-map'
9
+ import { CBOR } from './cbor'
10
+ import { validateQuery } from './internal'
11
+ import { performAsyncToWorkaroundNonAsyncFFIAPI } from './internal'
12
+ import { Bridge } from './bridge'
13
+
14
+ import type { DocumentID } from './document-id'
15
+ import type { QueryArguments, SortDirection } from './essentials'
16
+ import type { UpdateResult } from './update-result'
17
+ import type { CollectionInterface } from './collection-interface'
18
+
19
+ export abstract class BasePendingCursorOperation implements PromiseLike<Document[]> {
20
+ /**
21
+ * Removes all documents that match the query generated by the preceding
22
+ * function chaining.
23
+ *
24
+ * @returns An array promise containing the IDs of the documents that were
25
+ * removed.
26
+ */
27
+ abstract remove(): Promise<DocumentID[]>
28
+
29
+ /**
30
+ * Evicts all documents that match the query generated by the preceding
31
+ * function chaining.
32
+ *
33
+ * @return An array promise containing the IDs of the documents that were
34
+ * evicted.
35
+ */
36
+ abstract evict(): Promise<DocumentID[]>
37
+
38
+ /**
39
+ * Updates documents that match the query generated by the preceding function
40
+ * chaining.
41
+ *
42
+ * @param closure A closure that gets called with all of the documents
43
+ * matching the query. The documents are instances of {@link MutableDocument}
44
+ * so you can call update-related functions on them.
45
+ *
46
+ * @returns An {@link UpdateResultsMap} promise mapping document IDs to lists
47
+ * of {@link UpdateResult | update results} that describe the updates that
48
+ * were performed for each document.
49
+ */
50
+ abstract update(closure: (documents: MutableDocument[]) => void): Promise<UpdateResultsMap>
51
+
52
+ // ----------------------------------------------------------- Public --------
53
+
54
+ /** The query the receiver is operating with. */
55
+ readonly query: string
56
+
57
+ /** The named arguments for the {@link query}. */
58
+ readonly queryArgs: QueryArguments | null
59
+
60
+ /** The collection the receiver is operating on. */
61
+ readonly collection: CollectionInterface
62
+
63
+ /**
64
+ * Sorts the documents that match the query provided in the preceding
65
+ * `find`-like function call.
66
+ *
67
+ * @param query The query specifies the logic to be used when sorting the
68
+ * matching documents.
69
+ *
70
+ * @param direction Specify whether you want the sorting order to be
71
+ * `Ascending` or `Descending`.
72
+ *
73
+ * @return A cursor that you can chain further function calls and then either
74
+ * get the matching documents immediately or get updates about them over time.
75
+ */
76
+ sort(propertyPath: string, direction: SortDirection = 'ascending'): BasePendingCursorOperation {
77
+ this.orderBys.push({
78
+ query: propertyPath,
79
+ direction: direction === 'ascending' ? 'Ascending' : 'Descending',
80
+ })
81
+ return this
82
+ }
83
+
84
+ /**
85
+ * Offsets the resulting set of matching documents.
86
+ *
87
+ * This is useful if you aren't interested in the first N matching documents
88
+ * for one reason or another. For example, you might already have queried the
89
+ * collection and obtained the first 20 matching documents and so you might
90
+ * want to run the same query as you did previously but ignore the first 20
91
+ * matching documents, and that is when you would use `offset`.
92
+ *
93
+ * @param offset The number of matching documents that you want the eventual
94
+ * resulting set of matching documents to be offset by (and thus not include).
95
+ *
96
+ * @return A cursor that you can chain further function calls and then either
97
+ * get the matching documents immediately or get updates about them over time.
98
+ */
99
+ offset(offset: number): BasePendingCursorOperation {
100
+ if (offset < 0) throw new Error(`Can't offset by '${offset}', offset must be >= 0`)
101
+ if (!Number.isFinite(offset)) throw new Error(`Can't offset by '${offset}', offset must be a finite number`)
102
+ if (Number.isNaN(offset)) throw new Error(`Can't offset by '${offset}', offset must be a valid number`)
103
+
104
+ const integerOffset = Math.round(offset)
105
+ if (offset !== integerOffset) throw new Error(`Can't offset by '${offset}', offset must be an integer number`)
106
+
107
+ this.currentOffset = offset
108
+ return this
109
+ }
110
+
111
+ /**
112
+ * Limits the number of documents that get returned when querying a collection
113
+ * for matching documents.
114
+ *
115
+ * @param limit The maximum number of documents that will be returned.
116
+ *
117
+ * @return A cursor that you can chain further function calls and then either
118
+ * get the matching documents immediately or get updates about them over time.
119
+ */
120
+ limit(limit: number): BasePendingCursorOperation {
121
+ if (limit < -1) throw new Error(`Can't limit to '${limit}', limit must be >= -1 (where -1 means unlimited)`)
122
+ if (!Number.isFinite(limit)) throw new Error(`Can't limit to '${limit}', limit must be a finite number`)
123
+ if (Number.isNaN(limit)) throw new Error(`Can't limit to '${limit}', limit must be a valid number`)
124
+
125
+ const integerLimit = Math.round(limit)
126
+ if (limit !== integerLimit) throw new Error(`Can't limit to '${limit}', limit must be an integer number`)
127
+
128
+ this.currentLimit = limit
129
+ return this
130
+ }
131
+
132
+ /**
133
+ * Executes the query generated by the preceding function chaining and return
134
+ * the list of matching documents.
135
+ *
136
+ * @returns An array promise containing {@link Document | documents} matching
137
+ * the query generated by the preceding function chaining.
138
+ */
139
+ async exec(): Promise<Document[]> {
140
+ const ditto = this.collection.store.ditto
141
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
142
+ return ditto.deferCloseAsync(async () => {
143
+ const query = this.query
144
+
145
+ const documentPointers: FFI.Pointer<FFI.FFIDocument>[] = await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
146
+ return await FFI.collectionExecQueryStr(dittoHandle.deref(), this.collection.name, null, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset)
147
+ })
148
+
149
+ return documentPointers.map((documentPointer) => {
150
+ return Bridge.document.bridge(documentPointer)
151
+ })
152
+ })
153
+ }
154
+
155
+ // ----------------------------------------------------------- Internal ------
156
+
157
+ /**
158
+ * Updates documents that match the query generated by the preceding function
159
+ * chaining.
160
+ *
161
+ * @param closure A closure that gets called with all of the documents
162
+ * matching the query. The documents are instances of {@link MutableDocument}
163
+ * so you can call update-related functions on them.
164
+ * @param writeTransactionX a transaction to perform the operation in.
165
+ *
166
+ * @returns An {@link UpdateResultsMap} promise mapping document IDs to lists
167
+ * of {@link UpdateResult | update results} that describe the updates that
168
+ * were performed for each document.
169
+ * @internal
170
+ */
171
+ async updateWithTransaction(closure: (documents: MutableDocument[]) => void, writeTransactionX: FFI.Pointer<FFI.FFIWriteTransaction>): Promise<UpdateResultsMap> {
172
+ const ditto = this.collection.store.ditto
173
+ const dittoHandle = Bridge.ditto.handleFor(ditto)
174
+ return ditto.deferCloseAsync(async () => {
175
+ return await performAsyncToWorkaroundNonAsyncFFIAPI(async () => {
176
+ const query = this.query
177
+
178
+ const documentsX = await FFI.collectionExecQueryStr(dittoHandle.deref(), this.collection.name, writeTransactionX, query, this.queryArgsCBOR, this.orderBys, this.currentLimit, this.currentOffset)
179
+
180
+ const mutableDocuments: MutableDocument[] = documentsX.map((documentX) => {
181
+ return Bridge.mutableDocument.bridge(documentX, () => new MutableDocument())
182
+ })
183
+
184
+ closure(mutableDocuments)
185
+
186
+ const updateResultsDocumentIDs = []
187
+ const updateResultsByDocumentIDString: { [documentID: string]: UpdateResult[] } = {}
188
+
189
+ for (const mutableDocument of mutableDocuments) {
190
+ const documentID = mutableDocument.id
191
+ const documentIDString = documentID.toString()
192
+ const updateResults = mutableDocument['@ditto.updateResults']
193
+
194
+ if (updateResultsByDocumentIDString[documentIDString]) {
195
+ throw new Error(`Internal inconsistency, update results for document ID as string already exist: ${documentIDString}`)
196
+ }
197
+
198
+ updateResultsDocumentIDs.push(documentID)
199
+ updateResultsByDocumentIDString[documentIDString] = updateResults
200
+
201
+ Bridge.mutableDocument.unregister(mutableDocument)
202
+ }
203
+
204
+ // NOTE: ownership of documentsX (and contained documents)
205
+ // is transferred to Rust at this point.
206
+ await FFI.collectionUpdateMultiple(dittoHandle.deref(), this.collection.name, writeTransactionX, documentsX)
207
+
208
+ return new UpdateResultsMap(updateResultsDocumentIDs, updateResultsByDocumentIDString)
209
+ })
210
+ })
211
+ }
212
+
213
+ /** @internal */
214
+ constructor(query: string, queryArgs: QueryArguments | null, collection: CollectionInterface) {
215
+ this.query = validateQuery(query)
216
+ this.queryArgs = queryArgs ? Object.freeze({ ...queryArgs }) : null
217
+ this.collection = collection
218
+ this.queryArgsCBOR = queryArgs ? CBOR.encode(queryArgs) : null
219
+ }
220
+
221
+ /** @internal */
222
+ then<TResult1 = any, TResult2 = never>(onfulfilled?: ((value: any) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): PromiseLike<TResult1 | TResult2> {
223
+ return this.exec().then(onfulfilled, onrejected)
224
+ }
225
+
226
+ // ---------------------------------------------------------- Protected ------
227
+
228
+ /** @internal */
229
+ protected queryArgsCBOR: Uint8Array | null
230
+
231
+ /** @internal */
232
+ protected currentLimit = -1
233
+
234
+ /** @internal */
235
+ protected currentOffset = 0
236
+
237
+ /** @internal */
238
+ protected orderBys: FFI.OrderBy[] = []
239
+ }