@dittolive/ditto 4.7.4-rc.2 → 4.7.4

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 (160) hide show
  1. package/DittoReactNative.podspec +27 -0
  2. package/README.md +2 -2
  3. package/node/ditto.cjs.js +1 -1
  4. package/node/ditto.darwin-arm64.node +0 -0
  5. package/node/ditto.darwin-x64.node +0 -0
  6. package/node/ditto.linux-arm.node +0 -0
  7. package/node/ditto.linux-arm64.node +0 -0
  8. package/node/ditto.linux-x64.node +0 -0
  9. package/node/ditto.win32-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 +5 -2
  13. package/react-native/android/.gradle/8.9/checksums/checksums.lock +0 -0
  14. package/react-native/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  15. package/react-native/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  16. package/react-native/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  17. package/react-native/android/.gradle/8.9/gc.properties +0 -0
  18. package/react-native/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  19. package/react-native/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  20. package/react-native/android/.gradle/vcs-1/gc.properties +0 -0
  21. package/react-native/android/CMakeLists.txt +36 -0
  22. package/react-native/android/build.gradle +190 -0
  23. package/react-native/android/cpp-adapter.cpp +259 -0
  24. package/react-native/android/gradle.properties +5 -0
  25. package/react-native/android/src/main/AndroidManifest.xml +4 -0
  26. package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKModule.java +120 -0
  27. package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKPackage.java +28 -0
  28. package/react-native/cpp/include/Arc.hpp +159 -0
  29. package/react-native/cpp/include/Attachment.h +20 -0
  30. package/react-native/cpp/include/Authentication.h +23 -0
  31. package/react-native/cpp/include/Collection.h +13 -0
  32. package/react-native/cpp/include/ConnectionRequest.h +18 -0
  33. package/react-native/cpp/include/DQL.h +21 -0
  34. package/react-native/cpp/include/Document.h +17 -0
  35. package/react-native/cpp/include/FFIUtils.h +16 -0
  36. package/react-native/cpp/include/IO.h +13 -0
  37. package/react-native/cpp/include/Identity.h +17 -0
  38. package/react-native/cpp/include/Lifecycle.h +16 -0
  39. package/react-native/cpp/include/LiveQuery.h +17 -0
  40. package/react-native/cpp/include/Logger.h +22 -0
  41. package/react-native/cpp/include/Misc.h +30 -0
  42. package/react-native/cpp/include/Presence.h +18 -0
  43. package/react-native/cpp/include/SmallPeerInfo.h +19 -0
  44. package/react-native/cpp/include/Transports.h +25 -0
  45. package/react-native/cpp/include/TypedArray.hpp +167 -0
  46. package/react-native/cpp/include/Utils.h +70 -0
  47. package/react-native/cpp/include/main.h +10 -0
  48. package/react-native/cpp/src/Attachment.cpp +272 -0
  49. package/react-native/cpp/src/Authentication.cpp +227 -0
  50. package/react-native/cpp/src/Collection.cpp +56 -0
  51. package/react-native/cpp/src/ConnectionRequest.cpp +123 -0
  52. package/react-native/cpp/src/DQL.cpp +256 -0
  53. package/react-native/cpp/src/Document.cpp +146 -0
  54. package/react-native/cpp/src/FFIUtils.cpp +122 -0
  55. package/react-native/cpp/src/IO.cpp +35 -0
  56. package/react-native/cpp/src/Identity.cpp +122 -0
  57. package/react-native/cpp/src/Lifecycle.cpp +93 -0
  58. package/react-native/cpp/src/LiveQuery.cpp +63 -0
  59. package/react-native/cpp/src/Logger.cpp +199 -0
  60. package/react-native/cpp/src/Misc.cpp +322 -0
  61. package/react-native/cpp/src/Presence.cpp +166 -0
  62. package/react-native/cpp/src/SmallPeerInfo.cpp +142 -0
  63. package/react-native/cpp/src/Transports.cpp +275 -0
  64. package/react-native/cpp/src/TypedArray.cpp +303 -0
  65. package/react-native/cpp/src/Utils.cpp +139 -0
  66. package/react-native/cpp/src/main.cpp +178 -0
  67. package/react-native/dittoffi/dittoffi.h +4873 -0
  68. package/react-native/dittoffi/ifaddrs.cpp +385 -0
  69. package/react-native/dittoffi/ifaddrs.h +206 -0
  70. package/react-native/ios/DittoRNSDK.h +7 -0
  71. package/react-native/ios/DittoRNSDK.mm +159 -0
  72. package/react-native/ios/YeetJSIUtils.h +60 -0
  73. package/react-native/ios/YeetJSIUtils.mm +196 -0
  74. package/react-native/lib/commonjs/ditto.rn.js +93 -0
  75. package/react-native/lib/commonjs/ditto.rn.js.map +1 -0
  76. package/react-native/lib/commonjs/index.js +61 -0
  77. package/react-native/lib/commonjs/index.js.map +1 -0
  78. package/react-native/lib/module/ditto.rn.js +89 -0
  79. package/react-native/lib/module/ditto.rn.js.map +1 -0
  80. package/react-native/lib/module/index.js +27 -0
  81. package/react-native/lib/module/index.js.map +1 -0
  82. package/react-native/lib/typescript/ditto.rn.d.ts +15 -0
  83. package/react-native/lib/typescript/ditto.rn.d.ts.map +1 -0
  84. package/react-native/lib/typescript/index.d.ts +1 -0
  85. package/react-native/lib/typescript/index.d.ts.map +1 -0
  86. package/react-native/src/ditto.rn.ts +123 -0
  87. package/react-native/src/environment/environment.fallback.ts +4 -0
  88. package/react-native/src/index.ts +29 -0
  89. package/react-native/src/sources/@cbor-redux.ts +2 -0
  90. package/react-native/src/sources/@ditto.core.ts +1 -0
  91. package/react-native/src/sources/@environment.ts +1 -0
  92. package/react-native/src/sources/attachment-fetch-event.ts +54 -0
  93. package/react-native/src/sources/attachment-fetcher-manager.ts +145 -0
  94. package/react-native/src/sources/attachment-fetcher.ts +265 -0
  95. package/react-native/src/sources/attachment-token.ts +129 -0
  96. package/react-native/src/sources/attachment.ts +121 -0
  97. package/react-native/src/sources/augment.ts +108 -0
  98. package/react-native/src/sources/authenticator.ts +314 -0
  99. package/react-native/src/sources/base-pending-cursor-operation.ts +255 -0
  100. package/react-native/src/sources/base-pending-id-specific-operation.ts +112 -0
  101. package/react-native/src/sources/bridge.ts +557 -0
  102. package/react-native/src/sources/build-time-constants.ts +8 -0
  103. package/react-native/src/sources/cbor.ts +20 -0
  104. package/react-native/src/sources/collection-interface.ts +73 -0
  105. package/react-native/src/sources/collection.ts +219 -0
  106. package/react-native/src/sources/collections-event.ts +99 -0
  107. package/react-native/src/sources/connection-request.ts +142 -0
  108. package/react-native/src/sources/counter.ts +82 -0
  109. package/react-native/src/sources/ditto.ts +991 -0
  110. package/react-native/src/sources/document-id.ts +163 -0
  111. package/react-native/src/sources/document-path.ts +308 -0
  112. package/react-native/src/sources/document.ts +237 -0
  113. package/react-native/src/sources/epilogue.ts +32 -0
  114. package/react-native/src/sources/error-codes.ts +114 -0
  115. package/react-native/src/sources/error.ts +256 -0
  116. package/react-native/src/sources/essentials.ts +81 -0
  117. package/react-native/src/sources/ffi-error.ts +134 -0
  118. package/react-native/src/sources/ffi.ts +2190 -0
  119. package/react-native/src/sources/identity.ts +163 -0
  120. package/react-native/src/sources/init.ts +71 -0
  121. package/react-native/src/sources/internal.ts +143 -0
  122. package/react-native/src/sources/keep-alive.ts +73 -0
  123. package/react-native/src/sources/key-path.ts +198 -0
  124. package/react-native/src/sources/live-query-event.ts +208 -0
  125. package/react-native/src/sources/live-query-manager.ts +110 -0
  126. package/react-native/src/sources/live-query.ts +167 -0
  127. package/react-native/src/sources/logger.ts +196 -0
  128. package/react-native/src/sources/main.ts +61 -0
  129. package/react-native/src/sources/observer-manager.ts +185 -0
  130. package/react-native/src/sources/observer.ts +79 -0
  131. package/react-native/src/sources/pending-collections-operation.ts +241 -0
  132. package/react-native/src/sources/pending-cursor-operation.ts +218 -0
  133. package/react-native/src/sources/pending-id-specific-operation.ts +218 -0
  134. package/react-native/src/sources/presence-manager.ts +170 -0
  135. package/react-native/src/sources/presence.ts +427 -0
  136. package/react-native/src/sources/query-result-item.ts +131 -0
  137. package/react-native/src/sources/query-result.ts +55 -0
  138. package/react-native/src/sources/register.ts +95 -0
  139. package/react-native/src/sources/small-peer-info.ts +166 -0
  140. package/react-native/src/sources/static-tcp-client.ts +8 -0
  141. package/react-native/src/sources/store-observer.ts +170 -0
  142. package/react-native/src/sources/store.ts +630 -0
  143. package/react-native/src/sources/subscription-manager.ts +99 -0
  144. package/react-native/src/sources/subscription.ts +89 -0
  145. package/react-native/src/sources/sync-subscription.ts +90 -0
  146. package/react-native/src/sources/sync.ts +561 -0
  147. package/react-native/src/sources/test-helpers.ts +24 -0
  148. package/react-native/src/sources/transport-conditions-manager.ts +104 -0
  149. package/react-native/src/sources/transport-config.ts +430 -0
  150. package/react-native/src/sources/update-result.ts +66 -0
  151. package/react-native/src/sources/update-results-map.ts +65 -0
  152. package/react-native/src/sources/websocket-client.ts +7 -0
  153. package/react-native/src/sources/write-transaction-collection.ts +122 -0
  154. package/react-native/src/sources/write-transaction-pending-cursor-operation.ts +101 -0
  155. package/react-native/src/sources/write-transaction-pending-id-specific-operation.ts +74 -0
  156. package/react-native/src/sources/write-transaction.ts +121 -0
  157. package/react-native.config.js +9 -0
  158. package/web/ditto.es6.js +1 -1
  159. package/web/ditto.umd.js +1 -1
  160. package/web/ditto.wasm +0 -0
@@ -0,0 +1,32 @@
1
+ //
2
+ // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
+ //
4
+
5
+ // NOTE: this is the last file to be rolled up. Typically, you'll use this to
6
+ // tie up certain associations that would otherwise lead to import cycles. The
7
+ // bridges are a good example of this: the type constructors for the bridged
8
+ // types need to be registered with it, but can't do it at creation time of the
9
+ // bridges, because anything that would require a certain bridge would
10
+ // immediately require the bridged type, oftentimes leading to an import cycle.
11
+
12
+ import { Attachment } from './attachment'
13
+ import { Document, MutableDocument } from './document'
14
+
15
+ import { StaticTCPClient } from './static-tcp-client'
16
+ import { WebsocketClient } from './websocket-client'
17
+
18
+ import { Ditto } from './ditto'
19
+ import { Bridge } from './bridge'
20
+ import { ConnectionRequest } from './connection-request'
21
+ import { QueryResult } from './query-result'
22
+ import { QueryResultItem } from './query-result-item'
23
+
24
+ Bridge.attachment.registerType(Attachment)
25
+ Bridge.connectionRequest.registerType(ConnectionRequest)
26
+ Bridge.document.registerType(Document)
27
+ Bridge.queryResultItem.registerType(QueryResultItem)
28
+ Bridge.queryResult.registerType(QueryResult)
29
+ Bridge.mutableDocument.registerType(MutableDocument)
30
+ Bridge.staticTCPClient.registerType(StaticTCPClient)
31
+ Bridge.websocketClient.registerType(WebsocketClient)
32
+ Bridge.ditto.registerType(Ditto)
@@ -0,0 +1,114 @@
1
+ //
2
+ // Copyright © 2023 DittoLive Incorporated. All rights reserved.
3
+ //
4
+
5
+ export type ErrorCode = keyof typeof ERROR_CODES
6
+
7
+ /**
8
+ * Error code and default error message for all Ditto error messages.
9
+ *
10
+ * Keys of this object define _error codes_ with each value containing the
11
+ * accompanying error description that is set as the default error message for
12
+ * errors instantiated with this code.
13
+ *
14
+ * Error codes may start with an error domain and a slash to group categories of
15
+ * errors together.
16
+ */
17
+ export const ERROR_CODES = {
18
+ /** Internal error for unexpected system states */
19
+ internal: 'An unexpected internal error occurred. Please get in touch with Ditto customer service to report this incident.',
20
+
21
+ /** Internal error with an unknown error cause */
22
+ 'internal/unknown-error': 'An unexpected internal error occurred. Please get in touch with Ditto customer service to report this incident.',
23
+
24
+ //
25
+ // Errors originating in the SDK
26
+ //
27
+
28
+ /** Error when using a feature not supported by the current environment */
29
+ 'sdk/unsupported': 'The feature is not supported by the current environment.',
30
+
31
+ //
32
+ // Query errors
33
+ //
34
+
35
+ /** Error for invalid DQL query arguments. */
36
+ 'query/arguments-invalid': 'The query arguments were invalid.',
37
+
38
+ /** Errors that occur during execution of a query */
39
+ 'query/execution': 'The query could not be executed.',
40
+
41
+ /** Error for an invalid DQL query. */
42
+ 'query/invalid': 'The query was invalid.',
43
+
44
+ /** Error for a query that uses DQL features not supported in this version or not supported by the currently used API. */
45
+ 'query/unsupported': 'The query contains unsupported features.',
46
+
47
+ /** Error for a failed query updating system parameters */
48
+ 'query/parameter': 'The query to update system parameters failed.',
49
+
50
+ //
51
+ // Store errors
52
+ //
53
+
54
+ /** Error in the storage backend. */
55
+ 'store/backend': 'An error occurred with the storage backend.',
56
+
57
+ /** Error for an invalid CRDT. */
58
+ 'store/crdt': 'An error occurred processing a CRDT.',
59
+
60
+ /** Error for a document not found. */
61
+ 'store/document-not-found': 'The document with the provided ID could not be found.',
62
+
63
+ /** Permission has been denied for a file operation when working with attachments. */
64
+ 'store/attachment-file-permission-denied': 'Permission has been denied for a file operation when working with attachments.',
65
+
66
+ /** The source file for an attachment does not exist. */
67
+ 'store/attachment-file-not-found': 'The source file for the attachment does not exist.',
68
+
69
+ /** Attachment could not be found. */
70
+ 'store/attachment-not-found': 'The attachment could not be found.',
71
+
72
+ /** Attachment token is invalid. */
73
+ 'store/attachment-token-invalid': 'The attachment token is invalid.',
74
+
75
+ /** An unclassified error while creating an attachment. */
76
+ 'store/failed-to-create-attachment': 'The attachment could not be created.',
77
+
78
+ /** An unclassified error while fetching an attachment. */
79
+ 'store/failed-to-fetch-attachment': 'The attachment could not be fetched.',
80
+
81
+ //
82
+ // Activation errors
83
+ //
84
+
85
+ /** An error representing an invalid license token. */
86
+ 'activation/license-token-verification-failed': 'Please provide a valid license token.',
87
+
88
+ /** An error representing an expired license token. */
89
+ 'activation/license-token-expired': 'The license token expired. Please renew it.',
90
+
91
+ /** An error representing a token is in an unsupported future format. */
92
+ 'activation/license-token-unsupported-future-version': 'The provided license token is in an unsupported future format.',
93
+
94
+ /** The operation failed because it requires an activated Ditto instance. */
95
+ 'activation/not-activated': 'The operation failed because the Ditto instance has not yet been activated.',
96
+
97
+ /** A validation error where the maximum depth limit was exceeded. */
98
+ 'validation/depth-limit-exceeded': 'The maximum depth limit has been exceeded.',
99
+
100
+ /** A validation error where the value is not valid CBOR. */
101
+ 'validation/invalid-cbor': 'The value provided is not valid CBOR.',
102
+
103
+ /** A validation error where the value is not valid JSON. */
104
+ 'validation/invalid-json': 'The value provided is not valid JSON.',
105
+
106
+ /** A validation error where a value is required to be a JavaScript object */
107
+ 'validation/not-an-object': 'The value provided is not of type object.',
108
+
109
+ /** The value provided can not be serialized as JSON. */
110
+ 'validation/not-json-compatible': 'Value is not serializable as JSON.',
111
+
112
+ /** A validation error where a size limit was exceeded. */
113
+ 'validation/size-limit-exceeded': 'The size limit has been exceeded.',
114
+ } as const
@@ -0,0 +1,256 @@
1
+ //
2
+ // Copyright © 2023 DittoLive Incorporated. All rights reserved.
3
+ //
4
+
5
+ import { ErrorCode, ERROR_CODES } from './error-codes'
6
+ import { DittoFFIError } from './ffi-error'
7
+
8
+ /**
9
+ * Additional data, which provides context to the error and may help with
10
+ * debugging.
11
+ *
12
+ * **Warning:** The contents of this object may change in future versions of
13
+ * the SDK.
14
+ */
15
+ export type ErrorContext = Record<string, any>
16
+
17
+ /** @see {@link FFIErrorMapping} */
18
+ const DEFAULT_STATUS_CODE_MAPPING: FFIErrorMapping = {
19
+ //
20
+ // Activation errors
21
+ //
22
+ ActivationLicenseTokenExpired: ['activation/license-token-expired'],
23
+ ActivationLicenseTokenInvalid: ['activation/license-token-verification-failed'],
24
+ ActivationLicenseUnsupportedFutureVersion: ['activation/license-token-unsupported-future-version'],
25
+ ActivationNotActivated: ['activation/not-activated'],
26
+
27
+ //
28
+ // SDK-specific errors
29
+ //
30
+ JsFloatingStoreOperation: ['internal', 'Internal inconsistency, an outstanding store operation was not awaited.'],
31
+
32
+ //
33
+ // DQL errors
34
+ //
35
+ DqlQueryCompilation: ['query/invalid'],
36
+ DqlInvalidQueryArgs: ['query/arguments-invalid'],
37
+ DqlUnsupported: ['query/unsupported'],
38
+ StoreQuery: ['query/execution'],
39
+ ParameterQuery: ['query/parameter'],
40
+
41
+ //
42
+ // Store errors
43
+ //
44
+ StoreDocumentNotFound: ['store/document-not-found'],
45
+ StoreDatabase: ['store/backend'],
46
+ Crdt: ['store/crdt'],
47
+
48
+ //
49
+ // Validation errors
50
+ //
51
+ // This gets mapped to `internal` by default but depending on the context,
52
+ // it should be mapped to a more specific error code.
53
+ Base64Invalid: ['internal', 'Invalid base64 encoding.'],
54
+ ValidationDepthLimitExceeded: ['validation/depth-limit-exceeded'],
55
+ ValidationInvalidCbor: ['validation/invalid-cbor'],
56
+ ValidationInvalidJson: ['validation/invalid-json'],
57
+ ValidationNotAMap: ['validation/not-an-object'],
58
+ ValidationSizeLimitExceeded: ['validation/size-limit-exceeded'],
59
+
60
+ default: ['internal/unknown-error'],
61
+ }
62
+
63
+ /**
64
+ * `DittoError` is a subclass of the default Javascript `Error`. You can
65
+ * identify specific errors programatically using the
66
+ * {@link DittoError.code | code} property.
67
+ *
68
+ * Please reference {@link ERROR_CODES} for a comprehensive list of
69
+ * possible error codes in this SDK version.
70
+ *
71
+ * @example
72
+ * Handling a specific error code:
73
+ * ```typescript
74
+ * import { Attachment, DittoError } from '@dittolive/ditto'
75
+ *
76
+ * let attachment: Attachment
77
+ * try {
78
+ * attachment = await ditto.store.newAttachment(filePath)
79
+ * } catch (error) {
80
+ * if (error instanceof DittoError && error.code === 'store/attachment-file-not-found') {
81
+ * // Handle a non-existing file
82
+ * }
83
+ * throw error // Rethrow any other error
84
+ * }
85
+ * ```
86
+ */
87
+ export class DittoError extends Error {
88
+ /**
89
+ * Use the error code to identify a specific error programatically.
90
+ *
91
+ * @see {@link ERROR_CODES} for a comprehensive list of possible
92
+ * error codes in this SDK version.
93
+ */
94
+ readonly code: ErrorCode = 'internal'
95
+
96
+ /** Some environments provide a stack trace that is attached to any error. */
97
+ readonly stack?: string
98
+
99
+ /**
100
+ * Additional data, which provides context to the error and may help with
101
+ * debugging.
102
+ *
103
+ * **Warning:** The contents of this object may change in future versions of
104
+ * the SDK.
105
+ */
106
+ readonly context: Readonly<ErrorContext>
107
+
108
+ // --------------------------------------- Internal ------------------------
109
+
110
+ /**
111
+ * @internal
112
+ * @param code string error code, see {@link ERROR_CODES}
113
+ * @param message optional error message that replace's the message for the given error code
114
+ * @param context optional object containing data that can help debug this error
115
+ * @throws {@link DittoError} `internal`: when supplied with an invalid error code
116
+ */
117
+ constructor(code: ErrorCode, message?: string | null, context: ErrorContext = {}) {
118
+ if (ERROR_CODES[code] == null) {
119
+ throw new DittoError('internal', `Invalid error code: ${code}`)
120
+ }
121
+ super(message || ERROR_CODES[code])
122
+
123
+ this.code = code
124
+ this.context = Object.freeze({ ...context })
125
+ }
126
+
127
+ /**
128
+ * Create a {@link DittoError} from a {@link DittoFFIError}.
129
+ *
130
+ * The error message will be set to the optional `messageOverride` parameter
131
+ * if supplied, otherwise it will be taken from the `message` property of the
132
+ * given {@link DittoFFIError}. In any case, the details of the underlying FFI
133
+ * error are made available in the error context of the returned {@link DittoError}.
134
+ *
135
+ * @internal
136
+ * @param code string error code from {@link ERROR_CODES}
137
+ * @param messageOverride optional string that will be used as the error
138
+ * message even if a thread-local error message has been retrieved from the
139
+ * FFI
140
+ * @param context optional object containing data that can help debug this error
141
+ * @returns {@link DittoError}
142
+ */
143
+ static fromFFIError(ffiError: DittoFFIError, code: ErrorCode, messageOverride?: string, context?: ErrorContext): DittoError {
144
+ const message = messageOverride || ffiError.message
145
+ const errorContext = {
146
+ coreError: ffiError.code,
147
+ coreErrorMessage: ffiError.message,
148
+ ...context,
149
+ }
150
+ const err = new DittoError(code, message, errorContext)
151
+ if (ffiError.stack != null) {
152
+ // @ts-ignore
153
+ err.stack = ffiError.stack
154
+ }
155
+ return err
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Wraps the given function to catch any {@link DittoFFIError} and rethrow it as
161
+ * a {@link DittoError}, mapping status codes of the {@link DittoFFIError} to
162
+ * error codes of the {@link DittoError} using the given `statusCodeMapping`.
163
+ *
164
+ * Use either this function or {@link mapFFIErrorsAsync} to wrap all calls into
165
+ * the FFI that can fail.
166
+ *
167
+ * If the given status code mapping contains error messages, they will replace
168
+ * any error message that is returned by the FFI.
169
+ *
170
+ * @internal
171
+ * @param closure function that can throw a {@link DittoFFIError}
172
+ * @param statusCodeMapping optional mapping of status codes of the
173
+ * {@link DittoFFIError} to error codes of the {@link DittoError}
174
+ * @param context optional object containing data that can help debug this error
175
+ * @throws {@link DittoError} when the wrapped function throws a {@link DittoFFIError}
176
+ */
177
+ export function mapFFIErrors<T>(closure: () => T, statusCodeMapping?: FFIErrorMapping, context?: ErrorContext): T {
178
+ try {
179
+ return closure()
180
+ } catch (error) {
181
+ if (error instanceof DittoFFIError) {
182
+ throw translateFFIError(error, statusCodeMapping, context)
183
+ }
184
+ throw error
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Wraps the given async function to catch any {@link DittoFFIError} and rethrow
190
+ * it as a {@link DittoError}, mapping status codes of the {@link DittoFFIError}
191
+ * to error codes of the {@link DittoError} using the given `statusCodeMapping`.
192
+ *
193
+ * Use either this function or {@link mapFFIErrors} to wrap any calls into the
194
+ * FFI that can fail.
195
+ *
196
+ * If the given status code mapping contains error messages, they will replace
197
+ * any error message that is returned by the FFI.
198
+ *
199
+ * @internal
200
+ * @param closure async function that can throw a {@link DittoFFIError}
201
+ * @param statusCodeMapping optional mapping of status codes of the
202
+ * {@link DittoFFIError} to error codes of the {@link DittoError}.
203
+ * @param context optional object containing data that can help debug this error
204
+ * @throws {@link DittoError} when the wrapped function throws a {@link DittoFFIError}
205
+ */
206
+ export async function mapFFIErrorsAsync<T>(closure: () => Promise<T>, statusCodeMapping?: FFIErrorMapping, context?: ErrorContext): Promise<T> {
207
+ try {
208
+ return await closure()
209
+ } catch (error) {
210
+ if (error instanceof DittoFFIError) {
211
+ throw translateFFIError(error, statusCodeMapping, context)
212
+ }
213
+ throw error
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Defines which status code of an FFI response should be mapped to which error
219
+ * code of a {@link DittoError}.
220
+ *
221
+ * The second element of the tuple is an optional string that will be used as
222
+ * the error message of the {@link DittoError} instead of the FFI error message
223
+ * if defined.
224
+ *
225
+ * If a key `default` is present, it will be used as the error code if no
226
+ * mapping for a given status code is found.
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * const statusCodeMapping: FFIErrorMapping = {
231
+ * '1': ['activation/failed', 'It just didn\'t work out'],
232
+ * 'default': ['sdk/environment-incompatible']
233
+ * }
234
+ * ```
235
+ *
236
+ * @internal
237
+ */
238
+ type FFIErrorMapping = {
239
+ // statusCode needs to be `string` because it may be a negative number, which
240
+ // can't be used as an object key.
241
+ [statusCode: string]: [ErrorCode, string?]
242
+ }
243
+
244
+ const translateFFIError = (ffiError: DittoFFIError, customStatusCodeMapping?: FFIErrorMapping, context?: ErrorContext): DittoError => {
245
+ // Convert the status code to a string because it may be a negative number,
246
+ // which can't be used as an index into an object.
247
+ const statusCode = ffiError.code.toString()
248
+
249
+ let code: ErrorCode, message: string | undefined
250
+ if (customStatusCodeMapping != null && customStatusCodeMapping[statusCode] != null) {
251
+ ;[code, message] = customStatusCodeMapping[statusCode]
252
+ } else {
253
+ ;[code, message] = DEFAULT_STATUS_CODE_MAPPING[statusCode] ?? DEFAULT_STATUS_CODE_MAPPING.default
254
+ }
255
+ return DittoError.fromFFIError(ffiError, code, message, context)
256
+ }
@@ -0,0 +1,81 @@
1
+ //
2
+ // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
+ //
4
+
5
+ import { DocumentID } from './document-id'
6
+
7
+ import type { Attachment } from './attachment'
8
+
9
+ /**
10
+ * Describes the direction when sorting a query.
11
+ */
12
+ export type SortDirection = 'ascending' | 'descending'
13
+
14
+ /**
15
+ * Defines the various strategies available when inserting a document into a
16
+ * collection.
17
+ *
18
+ * - `merge`: the existing document will be merged with the document being
19
+ * inserted, if there is a pre-existing document.
20
+ *
21
+ * - `insertIfAbsent`: insert the document only if there is not already a
22
+ * document with the same ID in the store. If there is already a document in
23
+ * the store with the same ID then this will be a no-op.
24
+ *
25
+ * - `insertDefaultIfAbsent`: insert the document, with its contents treated as
26
+ * default data, only if there is not already a document with the same ID in
27
+ * the store. If there is already a document in the store with the same ID
28
+ * then this will be a no-op. Use this strategy if you want to insert default
29
+ * data for a given document ID, which you want to treat as common initial
30
+ * data amongst all peers and that you expect to be mutated or overwritten in
31
+ * due course.
32
+ */
33
+ // We support an additional write strategy, `updateDifferentValues`, but don't
34
+ // expose it in the public API of the JS SDK. This strategy was added to the
35
+ // Cocoa, Android, and .NET SDKs but is not necessarily something we want users to use.
36
+ // If we need to expose this strategy for JS in the future, it is already
37
+ // implemented and can be added to the union type and documentation to make it
38
+ // publicly available. c.f. #8638
39
+ export type WriteStrategy = 'merge' | 'insertIfAbsent' | 'insertDefaultIfAbsent'
40
+
41
+ /**
42
+ * Represents a dictionary of values to be incorporated into a query keyed by
43
+ * the placeholder used within that query. See method
44
+ * {@link Collection.find | find()} of {@link Collection} for more info.
45
+ *
46
+ * This value must not contain any non-finite numbers (`NaN`, `Infinity`,
47
+ * `-Infinity`).
48
+ */
49
+ // When CBOR-encoding a `QueryArguments` object, consider using
50
+ // `desugarJSObject()` to convert data to a JSON-compatible representation.
51
+ //
52
+ // NOTE: We decided not to upgrade this type to a more specific type alias
53
+ // because this type is only used in the query builder API which is in
54
+ // maintenance only mode.
55
+ export type QueryArguments = {
56
+ [key: string]: any
57
+ }
58
+
59
+ /**
60
+ * Values to be incorporated into a query, keyed by the placeholder used within
61
+ * that query.
62
+ *
63
+ * This value must not contain any non-finite numbers (`NaN`, `Infinity`,
64
+ * `-Infinity`).
65
+ */
66
+ // When CBOR-encoding a `DQLQueryArguments` object, consider using
67
+ // `desugarJSObject()` to convert data to a JSON-compatible representation.
68
+ //
69
+ // Internally, this supports all types supported by `ditto_types::Value`.
70
+ // https://github.com/getditto/ditto/blob/releases/stable/sdk-4.4/utils/types/src/value.rs#L267
71
+ //
72
+ // NOTE: This type contains `DocumentID` as the only exception to this rule,
73
+ // that should be removed once we remove the query builder API.
74
+ export type DQLQueryArguments = {
75
+ [key: string]: DQLQueryArgumentValue | undefined
76
+ }
77
+
78
+ /**
79
+ * Individual value in a {@link DQLQueryArguments} object.
80
+ */
81
+ export type DQLQueryArgumentValue = string | number | boolean | Uint8Array | null | Attachment | DocumentID | DQLQueryArgumentValue[] | { [key: string]: DQLQueryArgumentValue }
@@ -0,0 +1,134 @@
1
+ //
2
+ // Copyright © 2023 DittoLive Incorporated. All rights reserved.
3
+ //
4
+ // This internal module contains the Ditto FFI error type and helper functions
5
+ // for working with it. Except for the feature flag helper, this module should
6
+ // not depend on any other parts of the Ditto Javascript SDK.
7
+
8
+ import * as dittoCore from './@ditto.core'
9
+
10
+ import type { Pointer, FFIError } from './ffi'
11
+
12
+ /** Matches a `<...>` prefix in an FFI result error message, e.g. `<dql> ...`. **/
13
+ const PREFIX_REGEX = new RegExp(/^<.*?>\s*/)
14
+
15
+ /**
16
+ * Error codes for FFI result.
17
+ *
18
+ * This should include all variants of `FfiError` in `ffi/src/result/error.rs`.
19
+ *
20
+ * @internal
21
+ */
22
+ // prettier-ignore
23
+ export type FFIResultErrorCode =
24
+ 'ActivationLicenseTokenExpired' |
25
+ 'ActivationLicenseTokenInvalid' |
26
+ 'ActivationLicenseUnsupportedFutureVersion' |
27
+ 'ActivationNotActivated' |
28
+ 'Base64Invalid' |
29
+ 'Crdt' |
30
+ 'DqlInvalidQueryArgs' |
31
+ 'DqlQueryCompilation' |
32
+ 'DqlUnsupported' |
33
+ 'JsFloatingStoreOperation' |
34
+ 'StoreDatabase' |
35
+ 'StoreDocumentNotFound' |
36
+ 'StoreQuery' |
37
+ 'ValidationDepthLimitExceeded' |
38
+ 'ValidationInvalidCbor' |
39
+ 'ValidationInvalidJson' |
40
+ 'ValidationNotAMap' |
41
+ 'ValidationSizeLimitExceeded'
42
+
43
+ /**
44
+ * Represents an exception that occurred during a call into the Ditto FFI.
45
+ *
46
+ * Use the {@link throwOnErrorStatus | throwOnErrorStatus()} helper to
47
+ * automatically throw this error when an FFI call returns with a non-zero
48
+ * return value.
49
+ *
50
+ * @internal
51
+ */
52
+ export class DittoFFIError extends Error {
53
+ /**
54
+ * The code identifying this error.
55
+ *
56
+ * May be a numerical status code returned by an FFI call or an
57
+ * {@link FFIResultErrorCode} for errors returned on FFI result objects.
58
+ *
59
+ * @see {@link throwOnErrorStatus | throwOnErrorStatus()}
60
+ */
61
+ readonly code: number | FFIResultErrorCode
62
+
63
+ /**
64
+ * Only call this constructor after having called `FFI.ensureInitialized()`
65
+ * and `FFI.trace()`.
66
+ *
67
+ * @param code numerical status code returned by an FFI call or an
68
+ * {@link FFIResultErrorCode} for errors returned on FFI result objects
69
+ * @param messageOverride overrides the thread-local error message set in
70
+ * Ditto core
71
+ * @param messageFallback fallback message to use if the thread-local error
72
+ * message is empty
73
+ */
74
+ constructor(code: number | FFIResultErrorCode, messageOverride?: string, messageFallback?: string) {
75
+ // Call `ffiErrorMessage()` even when an override is provided to ensure that
76
+ // the thread-local error message is cleared.
77
+ const threadLocalErrorMessage = ffiErrorMessage()
78
+ super(messageOverride || threadLocalErrorMessage || messageFallback)
79
+ this.code = code
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Throws a {@link DittoFFIError} if the given `ffiError` is not `null`.
85
+ *
86
+ * Removes the thread-local error message from Ditto core. If an error description
87
+ * is available on the given `ffiError`, it is used as the error message of the
88
+ * thrown {@link DittoFFIError}. A prefix in the error message is removed, e.g.
89
+ * `<dql> ...`.
90
+ *
91
+ * If no error description is available, the given `ffiFunctionName` is used to
92
+ * produce a fallback error message.
93
+ *
94
+ * @internal
95
+ */
96
+ export function throwOnErrorResult(ffiError: Pointer<FFIError> | null, ffiFunctionName: string): void {
97
+ if (ffiError !== null) {
98
+ let errorCode: FFIResultErrorCode
99
+ let errorMsg: string | null
100
+ try {
101
+ errorCode = dittoCore.dittoffi_error_code(ffiError)
102
+ errorMsg = dittoCore.boxCStringIntoString(dittoCore.dittoffi_error_description(ffiError))
103
+ dittoCore.dittoffi_error_free(ffiError)
104
+ } catch (err: any) {
105
+ throw new DittoFFIError(-1, `Failed to retrieve Ditto core error message: ${err.message}`)
106
+ }
107
+
108
+ if (errorMsg == null) {
109
+ errorMsg = `${ffiFunctionName}() failed with error code: ${errorCode}`
110
+ } else {
111
+ // Remove prefix from error message, e.g. `<dql> ...`.
112
+ errorMsg = errorMsg.replace(PREFIX_REGEX, '')
113
+ }
114
+
115
+ throw new DittoFFIError(errorCode, errorMsg)
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Retrieves last thread-local error message and removes it.
121
+ *
122
+ * Subsequent call to this function (if no new error message has been set) will
123
+ * always return `null`.
124
+ *
125
+ * This function is not calling `FFI.ensureInitialized()` to avoid a circular
126
+ * dependency. This is okay as long as this function is only called from a
127
+ * context where `FFI.ensureInitialized()` has already been called.
128
+ *
129
+ * @internal
130
+ */
131
+ function ffiErrorMessage(): string | null {
132
+ const errorMessageCString = dittoCore.ditto_error_message()
133
+ return dittoCore.boxCStringIntoString(errorMessageCString)
134
+ }