@dittolive/ditto 4.7.4 → 4.7.5-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 (160) hide show
  1. package/README.md +2 -2
  2. package/node/ditto.cjs.js +1 -1
  3. package/node/ditto.darwin-arm64.node +0 -0
  4. package/node/ditto.darwin-x64.node +0 -0
  5. package/node/ditto.linux-arm.node +0 -0
  6. package/node/ditto.linux-arm64.node +0 -0
  7. package/node/ditto.linux-x64.node +0 -0
  8. package/node/ditto.win32-x64.node +0 -0
  9. package/node/transports.darwin-arm64.node +0 -0
  10. package/node/transports.darwin-x64.node +0 -0
  11. package/package.json +2 -5
  12. package/web/ditto.es6.js +1 -1
  13. package/web/ditto.umd.js +1 -1
  14. package/web/ditto.wasm +0 -0
  15. package/DittoReactNative.podspec +0 -27
  16. package/react-native/android/.gradle/8.9/checksums/checksums.lock +0 -0
  17. package/react-native/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  18. package/react-native/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  19. package/react-native/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  20. package/react-native/android/.gradle/8.9/gc.properties +0 -0
  21. package/react-native/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  22. package/react-native/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  23. package/react-native/android/.gradle/vcs-1/gc.properties +0 -0
  24. package/react-native/android/CMakeLists.txt +0 -36
  25. package/react-native/android/build.gradle +0 -190
  26. package/react-native/android/cpp-adapter.cpp +0 -259
  27. package/react-native/android/gradle.properties +0 -5
  28. package/react-native/android/src/main/AndroidManifest.xml +0 -4
  29. package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKModule.java +0 -120
  30. package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKPackage.java +0 -28
  31. package/react-native/cpp/include/Arc.hpp +0 -159
  32. package/react-native/cpp/include/Attachment.h +0 -20
  33. package/react-native/cpp/include/Authentication.h +0 -23
  34. package/react-native/cpp/include/Collection.h +0 -13
  35. package/react-native/cpp/include/ConnectionRequest.h +0 -18
  36. package/react-native/cpp/include/DQL.h +0 -21
  37. package/react-native/cpp/include/Document.h +0 -17
  38. package/react-native/cpp/include/FFIUtils.h +0 -16
  39. package/react-native/cpp/include/IO.h +0 -13
  40. package/react-native/cpp/include/Identity.h +0 -17
  41. package/react-native/cpp/include/Lifecycle.h +0 -16
  42. package/react-native/cpp/include/LiveQuery.h +0 -17
  43. package/react-native/cpp/include/Logger.h +0 -22
  44. package/react-native/cpp/include/Misc.h +0 -30
  45. package/react-native/cpp/include/Presence.h +0 -18
  46. package/react-native/cpp/include/SmallPeerInfo.h +0 -19
  47. package/react-native/cpp/include/Transports.h +0 -25
  48. package/react-native/cpp/include/TypedArray.hpp +0 -167
  49. package/react-native/cpp/include/Utils.h +0 -70
  50. package/react-native/cpp/include/main.h +0 -10
  51. package/react-native/cpp/src/Attachment.cpp +0 -272
  52. package/react-native/cpp/src/Authentication.cpp +0 -227
  53. package/react-native/cpp/src/Collection.cpp +0 -56
  54. package/react-native/cpp/src/ConnectionRequest.cpp +0 -123
  55. package/react-native/cpp/src/DQL.cpp +0 -256
  56. package/react-native/cpp/src/Document.cpp +0 -146
  57. package/react-native/cpp/src/FFIUtils.cpp +0 -122
  58. package/react-native/cpp/src/IO.cpp +0 -35
  59. package/react-native/cpp/src/Identity.cpp +0 -122
  60. package/react-native/cpp/src/Lifecycle.cpp +0 -93
  61. package/react-native/cpp/src/LiveQuery.cpp +0 -63
  62. package/react-native/cpp/src/Logger.cpp +0 -199
  63. package/react-native/cpp/src/Misc.cpp +0 -322
  64. package/react-native/cpp/src/Presence.cpp +0 -166
  65. package/react-native/cpp/src/SmallPeerInfo.cpp +0 -142
  66. package/react-native/cpp/src/Transports.cpp +0 -275
  67. package/react-native/cpp/src/TypedArray.cpp +0 -303
  68. package/react-native/cpp/src/Utils.cpp +0 -139
  69. package/react-native/cpp/src/main.cpp +0 -178
  70. package/react-native/dittoffi/dittoffi.h +0 -4873
  71. package/react-native/dittoffi/ifaddrs.cpp +0 -385
  72. package/react-native/dittoffi/ifaddrs.h +0 -206
  73. package/react-native/ios/DittoRNSDK.h +0 -7
  74. package/react-native/ios/DittoRNSDK.mm +0 -159
  75. package/react-native/ios/YeetJSIUtils.h +0 -60
  76. package/react-native/ios/YeetJSIUtils.mm +0 -196
  77. package/react-native/lib/commonjs/ditto.rn.js +0 -93
  78. package/react-native/lib/commonjs/ditto.rn.js.map +0 -1
  79. package/react-native/lib/commonjs/index.js +0 -61
  80. package/react-native/lib/commonjs/index.js.map +0 -1
  81. package/react-native/lib/module/ditto.rn.js +0 -89
  82. package/react-native/lib/module/ditto.rn.js.map +0 -1
  83. package/react-native/lib/module/index.js +0 -27
  84. package/react-native/lib/module/index.js.map +0 -1
  85. package/react-native/lib/typescript/ditto.rn.d.ts +0 -15
  86. package/react-native/lib/typescript/ditto.rn.d.ts.map +0 -1
  87. package/react-native/lib/typescript/index.d.ts +0 -1
  88. package/react-native/lib/typescript/index.d.ts.map +0 -1
  89. package/react-native/src/ditto.rn.ts +0 -123
  90. package/react-native/src/environment/environment.fallback.ts +0 -4
  91. package/react-native/src/index.ts +0 -29
  92. package/react-native/src/sources/@cbor-redux.ts +0 -2
  93. package/react-native/src/sources/@ditto.core.ts +0 -1
  94. package/react-native/src/sources/@environment.ts +0 -1
  95. package/react-native/src/sources/attachment-fetch-event.ts +0 -54
  96. package/react-native/src/sources/attachment-fetcher-manager.ts +0 -145
  97. package/react-native/src/sources/attachment-fetcher.ts +0 -265
  98. package/react-native/src/sources/attachment-token.ts +0 -129
  99. package/react-native/src/sources/attachment.ts +0 -121
  100. package/react-native/src/sources/augment.ts +0 -108
  101. package/react-native/src/sources/authenticator.ts +0 -314
  102. package/react-native/src/sources/base-pending-cursor-operation.ts +0 -255
  103. package/react-native/src/sources/base-pending-id-specific-operation.ts +0 -112
  104. package/react-native/src/sources/bridge.ts +0 -557
  105. package/react-native/src/sources/build-time-constants.ts +0 -8
  106. package/react-native/src/sources/cbor.ts +0 -20
  107. package/react-native/src/sources/collection-interface.ts +0 -73
  108. package/react-native/src/sources/collection.ts +0 -219
  109. package/react-native/src/sources/collections-event.ts +0 -99
  110. package/react-native/src/sources/connection-request.ts +0 -142
  111. package/react-native/src/sources/counter.ts +0 -82
  112. package/react-native/src/sources/ditto.ts +0 -991
  113. package/react-native/src/sources/document-id.ts +0 -163
  114. package/react-native/src/sources/document-path.ts +0 -308
  115. package/react-native/src/sources/document.ts +0 -237
  116. package/react-native/src/sources/epilogue.ts +0 -32
  117. package/react-native/src/sources/error-codes.ts +0 -114
  118. package/react-native/src/sources/error.ts +0 -256
  119. package/react-native/src/sources/essentials.ts +0 -81
  120. package/react-native/src/sources/ffi-error.ts +0 -134
  121. package/react-native/src/sources/ffi.ts +0 -2190
  122. package/react-native/src/sources/identity.ts +0 -163
  123. package/react-native/src/sources/init.ts +0 -71
  124. package/react-native/src/sources/internal.ts +0 -143
  125. package/react-native/src/sources/keep-alive.ts +0 -73
  126. package/react-native/src/sources/key-path.ts +0 -198
  127. package/react-native/src/sources/live-query-event.ts +0 -208
  128. package/react-native/src/sources/live-query-manager.ts +0 -110
  129. package/react-native/src/sources/live-query.ts +0 -167
  130. package/react-native/src/sources/logger.ts +0 -196
  131. package/react-native/src/sources/main.ts +0 -61
  132. package/react-native/src/sources/observer-manager.ts +0 -185
  133. package/react-native/src/sources/observer.ts +0 -79
  134. package/react-native/src/sources/pending-collections-operation.ts +0 -241
  135. package/react-native/src/sources/pending-cursor-operation.ts +0 -218
  136. package/react-native/src/sources/pending-id-specific-operation.ts +0 -218
  137. package/react-native/src/sources/presence-manager.ts +0 -170
  138. package/react-native/src/sources/presence.ts +0 -427
  139. package/react-native/src/sources/query-result-item.ts +0 -131
  140. package/react-native/src/sources/query-result.ts +0 -55
  141. package/react-native/src/sources/register.ts +0 -95
  142. package/react-native/src/sources/small-peer-info.ts +0 -166
  143. package/react-native/src/sources/static-tcp-client.ts +0 -8
  144. package/react-native/src/sources/store-observer.ts +0 -170
  145. package/react-native/src/sources/store.ts +0 -630
  146. package/react-native/src/sources/subscription-manager.ts +0 -99
  147. package/react-native/src/sources/subscription.ts +0 -89
  148. package/react-native/src/sources/sync-subscription.ts +0 -90
  149. package/react-native/src/sources/sync.ts +0 -561
  150. package/react-native/src/sources/test-helpers.ts +0 -24
  151. package/react-native/src/sources/transport-conditions-manager.ts +0 -104
  152. package/react-native/src/sources/transport-config.ts +0 -430
  153. package/react-native/src/sources/update-result.ts +0 -66
  154. package/react-native/src/sources/update-results-map.ts +0 -65
  155. package/react-native/src/sources/websocket-client.ts +0 -7
  156. package/react-native/src/sources/write-transaction-collection.ts +0 -122
  157. package/react-native/src/sources/write-transaction-pending-cursor-operation.ts +0 -101
  158. package/react-native/src/sources/write-transaction-pending-id-specific-operation.ts +0 -74
  159. package/react-native/src/sources/write-transaction.ts +0 -121
  160. package/react-native.config.js +0 -9
@@ -1,630 +0,0 @@
1
- //
2
- // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
- //
4
-
5
- import * as Environment from './@environment'
6
-
7
- import * as FFI from './ffi'
8
- import { Bridge } from './bridge'
9
-
10
- import { desugarJSObject } from './augment'
11
- import { Attachment, validateAttachmentMetadata } from './attachment'
12
- import { AttachmentFetcher } from './attachment-fetcher'
13
- import { AttachmentToken } from './attachment-token'
14
- import { CBOR } from './cbor'
15
- import { Collection } from './collection'
16
- import { StoreObserver } from './store-observer'
17
- import { DocumentID } from './document-id'
18
- import { DittoError, mapFFIErrors, mapFFIErrorsAsync } from './error'
19
- import { performAsyncToWorkaroundNonAsyncFFIAPI, step, validateQuery } from './internal'
20
- import { Logger } from './logger'
21
- import { PendingCollectionsOperation } from './pending-collections-operation'
22
- import { QueryResult } from './query-result'
23
- import { WriteTransaction } from './write-transaction'
24
-
25
- import type { TypedAttachmentToken } from './attachment-token'
26
- import type { StoreObservationHandlerWithSignalNext, StoreObservationHandler } from './store-observer'
27
- import type { Ditto } from './ditto'
28
- import type { Document } from './document'
29
- import type { DQLQueryArguments } from './essentials'
30
- import type { QueryResultItem } from './query-result-item'
31
- import type { WriteTransactionResult } from './write-transaction'
32
- import type { AttachmentMetadata } from './attachment'
33
- import type { AttachmentFetchEvent } from './attachment-fetch-event'
34
-
35
- /**
36
- * The entrypoint for all actions that relate to data stored by Ditto. Provides
37
- * access to collections, a write transaction API, and a query hash API.
38
- *
39
- * You don't create one directly but can access it from a particular
40
- * {@link Ditto} instance via its {@link Ditto.store | store} property.
41
- */
42
- export class Store {
43
- /** The {@link Ditto} instance this store belongs to. */
44
- readonly ditto: Ditto
45
-
46
- /**
47
- * All currently active store observers.
48
- *
49
- * **Note:** Manage store observers using
50
- * {@link registerObserver | registerObserver()} to register a new store
51
- * observer and {@link StoreObserver.cancel | StoreObserver.cancel()} to
52
- * remove an existing store observer.
53
- */
54
- readonly observers: Readonly<Array<StoreObserver>> = Object.freeze([])
55
-
56
- /**
57
- * All currently active attachment fetchers.
58
- *
59
- * **Note:** Manage attachment fetchers using
60
- * {@link fetchAttachment | fetchAttachment()} to start a new attachment fetch
61
- * and {@link AttachmentFetcher.stop | AttachmentFetcher.stop()} to cancel
62
- * an existing attachment fetch.
63
- */
64
- readonly attachmentFetchers: Readonly<Array<AttachmentFetcher>> = Object.freeze([])
65
-
66
- /**
67
- * Register a handler to be called whenever a query's results change in the
68
- * local store.
69
- *
70
- * Convenience method, same as
71
- * {@link registerObserverWithSignalNext | registerObserverWithSignalNext()},
72
- * except that here, the next invocation of the observation handler is
73
- * triggered automatically instead of having to call the passed in
74
- * `signalNext` function.
75
- *
76
- * @param query a string containing a valid query expressed in DQL.
77
- * @param observationHandler a function that is called whenever the query's results
78
- * change. The function is passed a {@link QueryResult} containing a
79
- * {@link QueryResultItem} for each match.
80
- * @param queryArguments an object of values keyed by the placeholder name
81
- * without the leading `:`. Example: `{ "name": "Joanna" }` for a query like
82
- * `SELECT * FROM people WHERE name = :name`.
83
- * @returns a {@link StoreObserver} that can be used to cancel the
84
- * observation.
85
- * @throws {@link DittoError} `query/invalid`: if `query` argument is not a
86
- * string or not valid DQL.
87
- * @throws {@link DittoError} `query/arguments-invalid`: if `queryArguments`
88
- * argument is invalid (e.g. contains unsupported types).
89
- * @throws {@link DittoError} `query/unsupported`: if the query is not a
90
- * `SELECT` query.
91
- * @throws {@link DittoError} may throw other errors.
92
- */
93
- registerObserver(query: string, observationHandler: StoreObservationHandler, queryArguments?: DQLQueryArguments): StoreObserver {
94
- const changeHandlerWithSignalNext: StoreObservationHandlerWithSignalNext = (queryResult: QueryResult, signalNext: () => void) => {
95
- try {
96
- observationHandler(queryResult)
97
- } finally {
98
- signalNext()
99
- }
100
- }
101
- return this.registerObserverWithSignalNext(query, changeHandlerWithSignalNext, queryArguments)
102
- }
103
-
104
- /**
105
- * Registers and returns a store observer for a query, configuring Ditto to
106
- * trigger the passed in observation handler whenever documents in the local
107
- * store change such that the result of the matching query changes. The passed
108
- * in query must be a `SELECT` query.
109
- *
110
- * Here, a function is passed as an additional argument to the observation
111
- * handler. Call this function as soon as the observation handler is ready to
112
- * process the the next change event. This allows the observation handler to
113
- * control how frequently it is called. See
114
- * {@link registerObserver | registerObserver()} for a convenience method that
115
- * automatically signals the next invocation.
116
- *
117
- * The first invocation of `observationHandler` will always happen after this
118
- * method has returned.
119
- *
120
- * @param query a string containing a valid query expressed in DQL.
121
- * @param observationHandler an observation handler function that is called
122
- * whenever the query's results change. The function is passed a
123
- * {@link QueryResult} containing a {@link QueryResultItem} for each match.
124
- * @param queryArguments an object of values keyed by the placeholder name
125
- * without the leading `:`. Example: `{ "name": "Joanna" }` for a query like
126
- * `SELECT * FROM people WHERE name = :name`.
127
- * @returns a {@link StoreObserver} that can be used to cancel the
128
- * observation.
129
- * @throws {@link DittoError} `query/invalid`: if `query` argument is not a
130
- * string or not valid DQL.
131
- * @throws {@link DittoError} `query/arguments-invalid`: if `queryArguments`
132
- * argument is invalid (e.g. contains unsupported types).
133
- * @throws {@link DittoError} `query/unsupported`: if the query is not a
134
- * `SELECT` query.
135
- * @throws {@link DittoError} may throw other errors.
136
- */
137
- registerObserverWithSignalNext(query: string, observationHandler: StoreObservationHandlerWithSignalNext, queryArguments?: DQLQueryArguments): StoreObserver {
138
- if (typeof query !== 'string') {
139
- throw new DittoError('query/invalid', `Expected parameter 'query' to be of type 'string', found: ${typeof query}`)
140
- }
141
-
142
- const storeObserver = new StoreObserver(this.ditto, query, queryArguments ?? null, observationHandler)
143
-
144
- // @ts-expect-error modifying readonly property
145
- this.observers = Object.freeze([...this.observers, storeObserver])
146
-
147
- // We have two requirements for this step: (1) we want to be able to wait
148
- // for the call to FFI to finish while closing ditto and (2) we want to
149
- // return from this function without waiting for the call to FFI to finish.
150
- // If we would await the call here, we could end up in a situation where
151
- // the first callback to the event handler is emitted before we return from
152
- // the method call that started the observer.
153
-
154
- const dittoHandle = Bridge.ditto.handleFor(this.ditto)
155
- void this.ditto.deferCloseAsync(async () => {
156
- return new Promise<void>((resolve) => {
157
- void step(async () => {
158
- try {
159
- // prettier-ignore
160
- await mapFFIErrorsAsync(
161
- async () => await FFI.liveQueryStart(dittoHandle.deref(), storeObserver.liveQueryID)
162
- )
163
- } catch (error: any) {
164
- // As this closure executes after the surrounding method has returned we don't throw
165
- // the error here. Instead we log the error.
166
- Logger.error(`Failed to start live query: ${error.message}`)
167
- }
168
- resolve()
169
- })
170
- })
171
- })
172
-
173
- return storeObserver
174
- }
175
-
176
- /**
177
- * Returns the collection for the given name. If the collection doesn't
178
- * exist yet, it will be created automatically as soon as the first
179
- * entry is inserted.
180
- * A collection name is valid if:
181
- * * its length is less than 100
182
- * * it is not empty
183
- * * it does not contain the char '\0'
184
- * * it does not begin with "$TS_"
185
- */
186
- collection(name: string): Collection {
187
- return new Collection(name, this)
188
- }
189
-
190
- /**
191
- * Returns an object that lets you fetch or observe the collections in the
192
- * store.
193
- *
194
- * @return A {@link PendingCollectionsOperation} object that you can use to
195
- * fetch or observe the collections in the store
196
- */
197
- collections(): PendingCollectionsOperation {
198
- return new PendingCollectionsOperation(this)
199
- }
200
-
201
- /**
202
- * Returns the names of all available collections in the store of the
203
- * related {@link Ditto} instance.
204
- */
205
- collectionNames(): Promise<string[]> {
206
- const ditto = this.ditto
207
- const dittoHandle = Bridge.ditto.handleFor(ditto)
208
- return ditto.deferClose(() => {
209
- return mapFFIErrors(() => FFI.dittoGetCollectionNames(dittoHandle.deref()))
210
- })
211
- }
212
-
213
- /**
214
- * Executes a DQL query and returns matching items as a query result.
215
- *
216
- * @param query a string containing a valid query expressed in DQL.
217
- * @param queryArguments an object of values keyed by the placeholder name
218
- * without the leading `:`. Example: `{ "name": "John" }` for a query like
219
- * `SELECT * FROM people WHERE name = :name`.
220
- * @returns a promise for a {@link QueryResult} containing a
221
- * {@link QueryResultItem} for each match.
222
- * @throws {@link DittoError} `query/invalid`: if `query` argument is not a
223
- * string or not valid DQL.
224
- * @throws {@link DittoError} `query/arguments-invalid`: if `queryArguments`
225
- * argument is invalid (e.g. contains unsupported types).
226
- * @throws {@link DittoError} may throw other errors.
227
- */
228
- async execute(query: string, queryArguments?: DQLQueryArguments): Promise<QueryResult> {
229
- if (typeof query !== 'string') {
230
- throw new DittoError('query/invalid', `Expected parameter 'query' to be of type 'string', found: ${typeof query}`)
231
- }
232
-
233
- const dittoHandle = Bridge.ditto.handleFor(this.ditto)
234
- return this.ditto.deferCloseAsync(async () => {
235
- // A one-off query execution uses a transaction internally but a
236
- // transaction API is not implemented yet.
237
- const writeTransaction = null
238
-
239
- let queryArgumentsCBOR: Uint8Array | null = null
240
- if (queryArguments != null) {
241
- try {
242
- const queryArgumentsJSON = desugarJSObject(queryArguments)
243
- queryArgumentsCBOR = CBOR.encode(queryArgumentsJSON)
244
- } catch (error: any) {
245
- throw new DittoError('query/arguments-invalid', `Unable to encode query arguments: ${error.message}`)
246
- }
247
- }
248
-
249
- // prettier-ignore
250
- const queryResultPointer: FFI.Pointer<FFI.FFIQueryResult> = await mapFFIErrorsAsync(
251
- async () => await performAsyncToWorkaroundNonAsyncFFIAPI(
252
- () => FFI.tryExecStatement(dittoHandle.deref(), writeTransaction, query, queryArgumentsCBOR)
253
- )
254
- )
255
-
256
- return Bridge.queryResult.bridge(queryResultPointer, () => new QueryResult(queryResultPointer))
257
- })
258
- }
259
-
260
- /**
261
- * Initiate a write transaction in a callback.
262
- *
263
- * Allows you to group multiple operations together that affect multiple documents, potentially across multiple collections.
264
- *
265
- * @param callback is given access to a {@link WriteTransaction | write transaction object} that can be used to perform operations on the store.
266
- * @returns a list of `WriteTransactionResult`s. There is a result for each operation performed as part of the write transaction.
267
- */
268
- async write(callback: (transaction: WriteTransaction) => Promise<void>): Promise<WriteTransactionResult[]> {
269
- // Run caller's callback, rolling back if needed.
270
- return this.ditto.deferCloseAsync(async () => {
271
- const transaction = await WriteTransaction.init(this.ditto)
272
-
273
- try {
274
- await callback(transaction)
275
- } catch (error: any) {
276
- await transaction.rollback()
277
- Logger.warning(`Transaction rolled back due to an error: ${error?.message}`)
278
- throw error
279
- }
280
- await transaction.commit()
281
-
282
- return transaction.results
283
- })
284
- }
285
-
286
- /**
287
- * Creates a new {@link Attachment} object, which can then be inserted into a
288
- * document.
289
- *
290
- * The file residing at the provided path will be copied into
291
- * Ditto's store.
292
- * The {@link Attachment} object that is returned is what you can then use to
293
- * insert an attachment into a document.
294
- *
295
- * **Note**: Relative paths for file sources are resolved from the current working
296
- * directory.
297
- *
298
- * You can provide metadata about the attachment, which will be replicated to
299
- * other peers alongside the file attachment.
300
- *
301
- * @example Inserting an attachment into a document
302
- * ```JavaScript
303
- * // Copy the file into Ditto's store and create an attachment object.
304
- * const attachment = await ditto.store.newAttachment(
305
- * '/path/to/my/file.pdf',
306
- * { my_field: 'optional metadata' }
307
- * )
308
- *
309
- * // Prepare the document value including the attachment.
310
- * const doc = {
311
- * _id: '123',
312
- * my_attachment: attachment,
313
- * other: 'some-string'
314
- * }
315
- *
316
- * // Insert the document into the collection, marking `my_attachment` as an
317
- * // attachment field.
318
- * await ditto.store.execute(
319
- * `INSERT INTO my_collection (my_attachment ATTACHMENT)
320
- * VALUES (:doc)`,
321
- * { doc }
322
- * )
323
- * ```
324
- *
325
- * @param pathOrData The path to the file that you want to create an
326
- * attachment with or the raw data.
327
- *
328
- * @param metadata Optional metadata that will be stored alongside the
329
- * attachment.
330
- *
331
- * @returns A promise for an {@link Attachment} object that can be used to
332
- * insert the attachment into a document.
333
- *
334
- * @throws {@link DittoError} `store/attachment-file-permission-denied` when
335
- * the file at the given path could not be read because of insufficient
336
- * permissions.
337
- *
338
- * @throws {@link DittoError} `store/attachment-file-not-found` when the file
339
- * at the given path could not be found.
340
- *
341
- * @throws {@link DittoError} `store/failed-to-create-attachment` when the
342
- * attachment could not be created for other reasons.
343
- *
344
- * @throws {@link DittoError} `sdk/unsupported` when trying to create an
345
- * attachment from a file path in a web browser.
346
- */
347
- async newAttachment(pathOrData: string | Uint8Array, metadata?: AttachmentMetadata): Promise<Attachment> {
348
- const ditto = this.ditto
349
- const dittoHandle = Bridge.ditto.handleFor(ditto)
350
-
351
- if (metadata != null) {
352
- validateAttachmentMetadata(metadata)
353
- }
354
-
355
- return ditto.deferCloseAsync(async () => {
356
- //
357
- // Create inputs for the attachment token from either a file path or raw data.
358
- //
359
- const { id, len, handle } = await (async () => {
360
- if (typeof pathOrData === 'string') {
361
- if (Environment.isWebBuild) {
362
- throw new DittoError('sdk/unsupported', `Can't create attachment from file when running in the browser. Please pass the raw data (as a Uint8Array) instead.`)
363
- }
364
-
365
- if (Environment.isNodeBuild || Environment.isReactNativeBuild) {
366
- return mapFFIErrors(() => FFI.dittoNewAttachmentFromFile(dittoHandle.deref(), pathOrData as string, 'Copy'), {
367
- 1: ['store/failed-to-create-attachment'],
368
- 2: ['store/attachment-file-not-found'],
369
- 3: ['store/attachment-file-permission-denied'],
370
- })
371
- }
372
- }
373
-
374
- if (pathOrData instanceof Uint8Array) {
375
- return mapFFIErrorsAsync(async () => await FFI.dittoNewAttachmentFromBytes(dittoHandle.deref(), pathOrData), {
376
- 1: ['store/failed-to-create-attachment'],
377
- })
378
- }
379
-
380
- throw new Error(`Can't create new attachment, only file path as string or raw data as Uint8Array are supported, but got: ${typeof pathOrData}, ${pathOrData}`)
381
- })()
382
-
383
- const attachmentTokenJSON: TypedAttachmentToken = { _id: id, _len: len, _meta: { ...metadata }, [FFI.DittoCRDTTypeKey]: FFI.DittoCRDTType.attachment }
384
-
385
- const attachmentToken = new AttachmentToken(attachmentTokenJSON)
386
- const attachment = new Attachment(ditto, attachmentToken)
387
-
388
- return Bridge.attachment.bridge(handle, () => attachment)
389
- })
390
- }
391
-
392
- /**
393
- * Trigger an attachment to be downloaded locally to the device and observe
394
- * its progress as it does so.
395
- *
396
- * When you encounter a document that contains an attachment, the attachment
397
- * will not automatically be downloaded along with the document. You trigger
398
- * an attachment to be downloaded locally to a device by calling this method.
399
- * It will report events relating to the attachment fetch attempt as it tries
400
- * to download it. The `eventHandler` block may be called multiple times with
401
- * progress events. It will then be called with either a `Completed` event or
402
- * a `Deleted` event. If downloading the attachment succeeds then the
403
- * `Completed` event that the `eventHandler` will be called with will hold a
404
- * reference to the downloaded attachment.
405
- *
406
- * The attachment to be fetched is identified by the `token` parameter. This
407
- * may either be an {@link AttachmentToken} instance received from a
408
- * {@link Document} or a plain object as is returned in a
409
- * {@link QueryResultItem} (see example below).
410
- *
411
- * @example Fetch an attachment from a document in the store
412
- *
413
- * ```js
414
- * // Fetch the attachment token from a document in the store
415
- * const result = await ditto.store.execute(`
416
- * SELECT *
417
- * FROM COLLECTION cars (my_attachment ATTACHMENT)
418
- * WHERE my_attachment.id = :id`,
419
- * { id: "123" }
420
- * )
421
- * const attachmentToken = result.items[0].my_attachment
422
- *
423
- * // Trigger the attachment to be downloaded
424
- * const attachment = await ditto.store.fetchAttachment(attachmentToken)
425
- *
426
- * // Extract the attachment data
427
- * const attachmentData = await attachment.data()
428
- * ```
429
- *
430
- * @param token The {@link AttachmentToken} instance or plain object
431
- * representation of the attachment to be downloaded.
432
- *
433
- * @param eventHandler An optional callback that will be called when there is
434
- * an update to the status of the attachment fetch attempt.
435
- *
436
- * @returns An {@link AttachmentFetcher} object, which is `PromiseLike` so
437
- * that you can `await` it to wait for the attachment to be downloaded. It
438
- * also provides a {@link AttachmentFetcher.stop | stop()} method to cancel
439
- * the fetch attempt.
440
- *
441
- * @throws {@link DittoError} `store/attachment-not-found` when the attachment
442
- * could not be found.
443
- *
444
- * @throws {@link DittoError} `store/attachment-token-invalid` when the given
445
- * attachment token is invalid.
446
- *
447
- * @throws {@link DittoError} `store/failed-to-fetch-attachment` when fetching
448
- * the attachment fails for other reasons.
449
- */
450
- fetchAttachment(token: AttachmentToken | { id: string; len: number | BigInt; metadata: AttachmentMetadata }, eventHandler?: (event: AttachmentFetchEvent) => void): AttachmentFetcher {
451
- if (token == null) {
452
- throw new Error("Missing required parameter 'token'")
453
- }
454
-
455
- let attachmentToken: AttachmentToken
456
- if (token instanceof AttachmentToken) {
457
- attachmentToken = token
458
- } else {
459
- attachmentToken = new AttachmentToken(token)
460
- }
461
-
462
- const ditto = this.ditto
463
- return ditto.deferClose(() => {
464
- const attachmentFetcher = new AttachmentFetcher(ditto, attachmentToken, null, eventHandler)
465
-
466
- // @ts-expect-error modifying readonly property
467
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
468
- this.attachmentFetchers = Object.freeze([...this.attachmentFetchers, attachmentFetcher])
469
-
470
- return attachmentFetcher
471
- })
472
- }
473
-
474
- // ----------------------------------------------------------- Internal ------
475
-
476
- /** @internal */
477
- constructor(ditto: Ditto) {
478
- this.ditto = ditto
479
- }
480
-
481
- /**
482
- * Registers a URL to be called whenever the given `SELECT` query observes
483
- * changes.
484
- *
485
- * No validation is performed on the URL, so it is up to the caller to ensure
486
- * that the URL is valid and can be reached.
487
- *
488
- * @internal
489
- * @returns a promise for a document id that acts as a webhook id
490
- * @throws {@link DittoError} `store/query-invalid`: if the query is invalid
491
- * @throws {@link DittoError} `store/query-arguments-invalid`: if the query arguments
492
- * are invalid
493
- * @throws {@link DittoError} `store/query-unsupported`: if the query is not a
494
- * `SELECT` query
495
- * @throws {@link DittoError} for any other error that occurs during query execution
496
- */
497
- async registerObserverWebhook(query: string, url: string, queryArguments?: DQLQueryArguments): Promise<DocumentID> {
498
- let queryArgumentsCBOR: Uint8Array | null = null
499
- if (queryArguments != null) {
500
- try {
501
- const queryArgumentsJSON = desugarJSObject(queryArguments)
502
- queryArgumentsCBOR = CBOR.encode(queryArgumentsJSON)
503
- } catch (error: any) {
504
- throw new DittoError('query/arguments-invalid', `Invalid query arguments: ${error.message}`)
505
- }
506
- }
507
-
508
- const dittoHandle = Bridge.ditto.handleFor(this.ditto)
509
- // prettier-ignore
510
- return this.ditto.deferCloseAsync(async () => {
511
- const webhookIDCBOR = await mapFFIErrorsAsync(
512
- async () => await FFI.tryRegisterStoreObserverWebhook(dittoHandle.deref(), query, queryArgumentsCBOR, url),
513
- )
514
- return new DocumentID(webhookIDCBOR, true)
515
- })
516
- }
517
-
518
- /**
519
- * Unregister a store observer. No-op if the change observer has already
520
- * been removed.
521
- *
522
- * This must only be called by the store observer itself.
523
- *
524
- * @param changeObserver the store observer to unregister
525
- * @returns true if the store observer was found and removed, false otherwise
526
- * @throws {@link DittoError} `internal`: if the store observer does not belong to
527
- * this store
528
- * @throws {@link DittoError} `internal`: if the store observer has not been
529
- * cancelled yet
530
- * @throws {@link DittoError} `internal`: for any other error that occurs while
531
- * trying to unregister the store observer
532
- * @internal
533
- */
534
- unregisterObserver(storeObserver: StoreObserver): boolean {
535
- if (storeObserver.ditto !== this.ditto) {
536
- throw new DittoError('internal', `Internal inconsistency, can't remove store observer that does not belong to this store`)
537
- }
538
-
539
- if (!storeObserver.isCancelled) {
540
- throw new DittoError('internal', "Internal inconsistency, can't remove store observer that has not been cancelled")
541
- }
542
-
543
- // Return early if the store observer has already been removed.
544
- const indexToDelete = this.observers.findIndex((observer) => observer === storeObserver)
545
- if (indexToDelete === -1) {
546
- return false
547
- }
548
-
549
- const newObservers = [...this.observers]
550
- newObservers.splice(indexToDelete, 1)
551
- // @ts-expect-error modifying readonly property
552
- this.observers = Object.freeze(newObservers)
553
-
554
- const dittoHandle = Bridge.ditto.handleFor(this.ditto)
555
- this.ditto.deferClose(() => {
556
- // prettier-ignore
557
- mapFFIErrors(
558
- () => FFI.liveQueryStop(dittoHandle.deref(), storeObserver.liveQueryID)
559
- )
560
- })
561
- return true
562
- }
563
-
564
- /**
565
- * Remove an attachment fetcher that is owned by this store. No-op if the
566
- * attachment fetcher has already been removed.
567
- *
568
- * This must only be called by the attachment fetcher itself.
569
- *
570
- * @param attachmentFetcher the attachment fetcher to finalize
571
- * @returns true if the attachment fetcher was found and removed, false
572
- * otherwise
573
- * @internal
574
- */
575
- removeAttachmentFetcher(attachmentFetcher: AttachmentFetcher): boolean {
576
- if (attachmentFetcher.ditto !== this.ditto) {
577
- throw new DittoError('internal', `Internal inconsistency, can't finalize attachment fetcher that does not belong to this store`)
578
- }
579
-
580
- if (attachmentFetcher.manager != null) {
581
- throw new DittoError('internal', "Internal inconsistency, store can't remove attachment fetcher that is owned by the attachment fetcher manager")
582
- }
583
-
584
- if (!attachmentFetcher.isStopped) {
585
- throw new DittoError('internal', "Internal inconsistency, can't remove attachment fetcher that has not stopped")
586
- }
587
-
588
- const indexToDelete = this.attachmentFetchers.findIndex((fetcher) => fetcher === attachmentFetcher)
589
- if (indexToDelete === -1) {
590
- return false
591
- }
592
-
593
- const newAttachmentFetchers = [...this.attachmentFetchers]
594
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
595
- newAttachmentFetchers.splice(indexToDelete, 1)
596
- // @ts-expect-error modifying readonly property
597
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
598
- this.attachmentFetchers = Object.freeze(newAttachmentFetchers)
599
-
600
- return true
601
- }
602
-
603
- /** @internal */
604
- close() {
605
- for (const observer of this.observers) {
606
- observer.cancel()
607
- }
608
-
609
- for (const fetcher of this.attachmentFetchers) {
610
- fetcher.stop()
611
- }
612
-
613
- // NOTE: live query webhook is taken care of by the FFI's
614
- // `ditto_shutdown()`, no need to unregister it here.
615
- }
616
-
617
- /**
618
- * Private method, used only by the Portal https://github.com/getditto/ditto/pull/3652
619
- * @internal
620
- */
621
- async registerLiveQueryWebhook(collectionName: string, query: string, url: string): Promise<DocumentID> {
622
- const ditto = this.ditto
623
- const dittoHandle = Bridge.ditto.handleFor(ditto)
624
- return ditto.deferCloseAsync(async () => {
625
- const validatedQuery = validateQuery(query)
626
- const idCBOR = await FFI.liveQueryWebhookRegister(dittoHandle.deref(), collectionName, validatedQuery, [], 0, 0, url)
627
- return new DocumentID(idCBOR, true)
628
- })
629
- }
630
- }