@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.
- package/README.md +2 -2
- package/node/ditto.cjs.js +1 -1
- package/node/ditto.darwin-arm64.node +0 -0
- package/node/ditto.darwin-x64.node +0 -0
- package/node/ditto.linux-arm.node +0 -0
- package/node/ditto.linux-arm64.node +0 -0
- package/node/ditto.linux-x64.node +0 -0
- package/node/ditto.win32-x64.node +0 -0
- package/node/transports.darwin-arm64.node +0 -0
- package/node/transports.darwin-x64.node +0 -0
- package/package.json +2 -5
- package/web/ditto.es6.js +1 -1
- package/web/ditto.umd.js +1 -1
- package/web/ditto.wasm +0 -0
- package/DittoReactNative.podspec +0 -27
- package/react-native/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/react-native/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/react-native/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/react-native/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/react-native/android/.gradle/8.9/gc.properties +0 -0
- package/react-native/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/react-native/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/react-native/android/.gradle/vcs-1/gc.properties +0 -0
- package/react-native/android/CMakeLists.txt +0 -36
- package/react-native/android/build.gradle +0 -190
- package/react-native/android/cpp-adapter.cpp +0 -259
- package/react-native/android/gradle.properties +0 -5
- package/react-native/android/src/main/AndroidManifest.xml +0 -4
- package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKModule.java +0 -120
- package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKPackage.java +0 -28
- package/react-native/cpp/include/Arc.hpp +0 -159
- package/react-native/cpp/include/Attachment.h +0 -20
- package/react-native/cpp/include/Authentication.h +0 -23
- package/react-native/cpp/include/Collection.h +0 -13
- package/react-native/cpp/include/ConnectionRequest.h +0 -18
- package/react-native/cpp/include/DQL.h +0 -21
- package/react-native/cpp/include/Document.h +0 -17
- package/react-native/cpp/include/FFIUtils.h +0 -16
- package/react-native/cpp/include/IO.h +0 -13
- package/react-native/cpp/include/Identity.h +0 -17
- package/react-native/cpp/include/Lifecycle.h +0 -16
- package/react-native/cpp/include/LiveQuery.h +0 -17
- package/react-native/cpp/include/Logger.h +0 -22
- package/react-native/cpp/include/Misc.h +0 -30
- package/react-native/cpp/include/Presence.h +0 -18
- package/react-native/cpp/include/SmallPeerInfo.h +0 -19
- package/react-native/cpp/include/Transports.h +0 -25
- package/react-native/cpp/include/TypedArray.hpp +0 -167
- package/react-native/cpp/include/Utils.h +0 -70
- package/react-native/cpp/include/main.h +0 -10
- package/react-native/cpp/src/Attachment.cpp +0 -272
- package/react-native/cpp/src/Authentication.cpp +0 -227
- package/react-native/cpp/src/Collection.cpp +0 -56
- package/react-native/cpp/src/ConnectionRequest.cpp +0 -123
- package/react-native/cpp/src/DQL.cpp +0 -256
- package/react-native/cpp/src/Document.cpp +0 -146
- package/react-native/cpp/src/FFIUtils.cpp +0 -122
- package/react-native/cpp/src/IO.cpp +0 -35
- package/react-native/cpp/src/Identity.cpp +0 -122
- package/react-native/cpp/src/Lifecycle.cpp +0 -93
- package/react-native/cpp/src/LiveQuery.cpp +0 -63
- package/react-native/cpp/src/Logger.cpp +0 -199
- package/react-native/cpp/src/Misc.cpp +0 -322
- package/react-native/cpp/src/Presence.cpp +0 -166
- package/react-native/cpp/src/SmallPeerInfo.cpp +0 -142
- package/react-native/cpp/src/Transports.cpp +0 -275
- package/react-native/cpp/src/TypedArray.cpp +0 -303
- package/react-native/cpp/src/Utils.cpp +0 -139
- package/react-native/cpp/src/main.cpp +0 -178
- package/react-native/dittoffi/dittoffi.h +0 -4873
- package/react-native/dittoffi/ifaddrs.cpp +0 -385
- package/react-native/dittoffi/ifaddrs.h +0 -206
- package/react-native/ios/DittoRNSDK.h +0 -7
- package/react-native/ios/DittoRNSDK.mm +0 -159
- package/react-native/ios/YeetJSIUtils.h +0 -60
- package/react-native/ios/YeetJSIUtils.mm +0 -196
- package/react-native/lib/commonjs/ditto.rn.js +0 -93
- package/react-native/lib/commonjs/ditto.rn.js.map +0 -1
- package/react-native/lib/commonjs/index.js +0 -61
- package/react-native/lib/commonjs/index.js.map +0 -1
- package/react-native/lib/module/ditto.rn.js +0 -89
- package/react-native/lib/module/ditto.rn.js.map +0 -1
- package/react-native/lib/module/index.js +0 -27
- package/react-native/lib/module/index.js.map +0 -1
- package/react-native/lib/typescript/ditto.rn.d.ts +0 -15
- package/react-native/lib/typescript/ditto.rn.d.ts.map +0 -1
- package/react-native/lib/typescript/index.d.ts +0 -1
- package/react-native/lib/typescript/index.d.ts.map +0 -1
- package/react-native/src/ditto.rn.ts +0 -123
- package/react-native/src/environment/environment.fallback.ts +0 -4
- package/react-native/src/index.ts +0 -29
- package/react-native/src/sources/@cbor-redux.ts +0 -2
- package/react-native/src/sources/@ditto.core.ts +0 -1
- package/react-native/src/sources/@environment.ts +0 -1
- package/react-native/src/sources/attachment-fetch-event.ts +0 -54
- package/react-native/src/sources/attachment-fetcher-manager.ts +0 -145
- package/react-native/src/sources/attachment-fetcher.ts +0 -265
- package/react-native/src/sources/attachment-token.ts +0 -129
- package/react-native/src/sources/attachment.ts +0 -121
- package/react-native/src/sources/augment.ts +0 -108
- package/react-native/src/sources/authenticator.ts +0 -314
- package/react-native/src/sources/base-pending-cursor-operation.ts +0 -255
- package/react-native/src/sources/base-pending-id-specific-operation.ts +0 -112
- package/react-native/src/sources/bridge.ts +0 -557
- package/react-native/src/sources/build-time-constants.ts +0 -8
- package/react-native/src/sources/cbor.ts +0 -20
- package/react-native/src/sources/collection-interface.ts +0 -73
- package/react-native/src/sources/collection.ts +0 -219
- package/react-native/src/sources/collections-event.ts +0 -99
- package/react-native/src/sources/connection-request.ts +0 -142
- package/react-native/src/sources/counter.ts +0 -82
- package/react-native/src/sources/ditto.ts +0 -991
- package/react-native/src/sources/document-id.ts +0 -163
- package/react-native/src/sources/document-path.ts +0 -308
- package/react-native/src/sources/document.ts +0 -237
- package/react-native/src/sources/epilogue.ts +0 -32
- package/react-native/src/sources/error-codes.ts +0 -114
- package/react-native/src/sources/error.ts +0 -256
- package/react-native/src/sources/essentials.ts +0 -81
- package/react-native/src/sources/ffi-error.ts +0 -134
- package/react-native/src/sources/ffi.ts +0 -2190
- package/react-native/src/sources/identity.ts +0 -163
- package/react-native/src/sources/init.ts +0 -71
- package/react-native/src/sources/internal.ts +0 -143
- package/react-native/src/sources/keep-alive.ts +0 -73
- package/react-native/src/sources/key-path.ts +0 -198
- package/react-native/src/sources/live-query-event.ts +0 -208
- package/react-native/src/sources/live-query-manager.ts +0 -110
- package/react-native/src/sources/live-query.ts +0 -167
- package/react-native/src/sources/logger.ts +0 -196
- package/react-native/src/sources/main.ts +0 -61
- package/react-native/src/sources/observer-manager.ts +0 -185
- package/react-native/src/sources/observer.ts +0 -79
- package/react-native/src/sources/pending-collections-operation.ts +0 -241
- package/react-native/src/sources/pending-cursor-operation.ts +0 -218
- package/react-native/src/sources/pending-id-specific-operation.ts +0 -218
- package/react-native/src/sources/presence-manager.ts +0 -170
- package/react-native/src/sources/presence.ts +0 -427
- package/react-native/src/sources/query-result-item.ts +0 -131
- package/react-native/src/sources/query-result.ts +0 -55
- package/react-native/src/sources/register.ts +0 -95
- package/react-native/src/sources/small-peer-info.ts +0 -166
- package/react-native/src/sources/static-tcp-client.ts +0 -8
- package/react-native/src/sources/store-observer.ts +0 -170
- package/react-native/src/sources/store.ts +0 -630
- package/react-native/src/sources/subscription-manager.ts +0 -99
- package/react-native/src/sources/subscription.ts +0 -89
- package/react-native/src/sources/sync-subscription.ts +0 -90
- package/react-native/src/sources/sync.ts +0 -561
- package/react-native/src/sources/test-helpers.ts +0 -24
- package/react-native/src/sources/transport-conditions-manager.ts +0 -104
- package/react-native/src/sources/transport-config.ts +0 -430
- package/react-native/src/sources/update-result.ts +0 -66
- package/react-native/src/sources/update-results-map.ts +0 -65
- package/react-native/src/sources/websocket-client.ts +0 -7
- package/react-native/src/sources/write-transaction-collection.ts +0 -122
- package/react-native/src/sources/write-transaction-pending-cursor-operation.ts +0 -101
- package/react-native/src/sources/write-transaction-pending-id-specific-operation.ts +0 -74
- package/react-native/src/sources/write-transaction.ts +0 -121
- 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
|
-
}
|