@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.
- package/DittoReactNative.podspec +27 -0
- 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 +5 -2
- 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 +2 -0
- package/react-native/android/.gradle/vcs-1/gc.properties +0 -0
- package/react-native/android/CMakeLists.txt +36 -0
- package/react-native/android/build.gradle +190 -0
- package/react-native/android/cpp-adapter.cpp +259 -0
- package/react-native/android/gradle.properties +5 -0
- package/react-native/android/src/main/AndroidManifest.xml +4 -0
- package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKModule.java +120 -0
- package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKPackage.java +28 -0
- package/react-native/cpp/include/Arc.hpp +159 -0
- package/react-native/cpp/include/Attachment.h +20 -0
- package/react-native/cpp/include/Authentication.h +23 -0
- package/react-native/cpp/include/Collection.h +13 -0
- package/react-native/cpp/include/ConnectionRequest.h +18 -0
- package/react-native/cpp/include/DQL.h +21 -0
- package/react-native/cpp/include/Document.h +17 -0
- package/react-native/cpp/include/FFIUtils.h +16 -0
- package/react-native/cpp/include/IO.h +13 -0
- package/react-native/cpp/include/Identity.h +17 -0
- package/react-native/cpp/include/Lifecycle.h +16 -0
- package/react-native/cpp/include/LiveQuery.h +17 -0
- package/react-native/cpp/include/Logger.h +22 -0
- package/react-native/cpp/include/Misc.h +30 -0
- package/react-native/cpp/include/Presence.h +18 -0
- package/react-native/cpp/include/SmallPeerInfo.h +19 -0
- package/react-native/cpp/include/Transports.h +25 -0
- package/react-native/cpp/include/TypedArray.hpp +167 -0
- package/react-native/cpp/include/Utils.h +70 -0
- package/react-native/cpp/include/main.h +10 -0
- package/react-native/cpp/src/Attachment.cpp +272 -0
- package/react-native/cpp/src/Authentication.cpp +227 -0
- package/react-native/cpp/src/Collection.cpp +56 -0
- package/react-native/cpp/src/ConnectionRequest.cpp +123 -0
- package/react-native/cpp/src/DQL.cpp +256 -0
- package/react-native/cpp/src/Document.cpp +146 -0
- package/react-native/cpp/src/FFIUtils.cpp +122 -0
- package/react-native/cpp/src/IO.cpp +35 -0
- package/react-native/cpp/src/Identity.cpp +122 -0
- package/react-native/cpp/src/Lifecycle.cpp +93 -0
- package/react-native/cpp/src/LiveQuery.cpp +63 -0
- package/react-native/cpp/src/Logger.cpp +199 -0
- package/react-native/cpp/src/Misc.cpp +322 -0
- package/react-native/cpp/src/Presence.cpp +166 -0
- package/react-native/cpp/src/SmallPeerInfo.cpp +142 -0
- package/react-native/cpp/src/Transports.cpp +275 -0
- package/react-native/cpp/src/TypedArray.cpp +303 -0
- package/react-native/cpp/src/Utils.cpp +139 -0
- package/react-native/cpp/src/main.cpp +178 -0
- package/react-native/dittoffi/dittoffi.h +4873 -0
- package/react-native/dittoffi/ifaddrs.cpp +385 -0
- package/react-native/dittoffi/ifaddrs.h +206 -0
- package/react-native/ios/DittoRNSDK.h +7 -0
- package/react-native/ios/DittoRNSDK.mm +159 -0
- package/react-native/ios/YeetJSIUtils.h +60 -0
- package/react-native/ios/YeetJSIUtils.mm +196 -0
- package/react-native/lib/commonjs/ditto.rn.js +93 -0
- package/react-native/lib/commonjs/ditto.rn.js.map +1 -0
- package/react-native/lib/commonjs/index.js +61 -0
- package/react-native/lib/commonjs/index.js.map +1 -0
- package/react-native/lib/module/ditto.rn.js +89 -0
- package/react-native/lib/module/ditto.rn.js.map +1 -0
- package/react-native/lib/module/index.js +27 -0
- package/react-native/lib/module/index.js.map +1 -0
- package/react-native/lib/typescript/ditto.rn.d.ts +15 -0
- package/react-native/lib/typescript/ditto.rn.d.ts.map +1 -0
- package/react-native/lib/typescript/index.d.ts +1 -0
- package/react-native/lib/typescript/index.d.ts.map +1 -0
- package/react-native/src/ditto.rn.ts +123 -0
- package/react-native/src/environment/environment.fallback.ts +4 -0
- package/react-native/src/index.ts +29 -0
- package/react-native/src/sources/@cbor-redux.ts +2 -0
- package/react-native/src/sources/@ditto.core.ts +1 -0
- package/react-native/src/sources/@environment.ts +1 -0
- package/react-native/src/sources/attachment-fetch-event.ts +54 -0
- package/react-native/src/sources/attachment-fetcher-manager.ts +145 -0
- package/react-native/src/sources/attachment-fetcher.ts +265 -0
- package/react-native/src/sources/attachment-token.ts +129 -0
- package/react-native/src/sources/attachment.ts +121 -0
- package/react-native/src/sources/augment.ts +108 -0
- package/react-native/src/sources/authenticator.ts +314 -0
- package/react-native/src/sources/base-pending-cursor-operation.ts +255 -0
- package/react-native/src/sources/base-pending-id-specific-operation.ts +112 -0
- package/react-native/src/sources/bridge.ts +557 -0
- package/react-native/src/sources/build-time-constants.ts +8 -0
- package/react-native/src/sources/cbor.ts +20 -0
- package/react-native/src/sources/collection-interface.ts +73 -0
- package/react-native/src/sources/collection.ts +219 -0
- package/react-native/src/sources/collections-event.ts +99 -0
- package/react-native/src/sources/connection-request.ts +142 -0
- package/react-native/src/sources/counter.ts +82 -0
- package/react-native/src/sources/ditto.ts +991 -0
- package/react-native/src/sources/document-id.ts +163 -0
- package/react-native/src/sources/document-path.ts +308 -0
- package/react-native/src/sources/document.ts +237 -0
- package/react-native/src/sources/epilogue.ts +32 -0
- package/react-native/src/sources/error-codes.ts +114 -0
- package/react-native/src/sources/error.ts +256 -0
- package/react-native/src/sources/essentials.ts +81 -0
- package/react-native/src/sources/ffi-error.ts +134 -0
- package/react-native/src/sources/ffi.ts +2190 -0
- package/react-native/src/sources/identity.ts +163 -0
- package/react-native/src/sources/init.ts +71 -0
- package/react-native/src/sources/internal.ts +143 -0
- package/react-native/src/sources/keep-alive.ts +73 -0
- package/react-native/src/sources/key-path.ts +198 -0
- package/react-native/src/sources/live-query-event.ts +208 -0
- package/react-native/src/sources/live-query-manager.ts +110 -0
- package/react-native/src/sources/live-query.ts +167 -0
- package/react-native/src/sources/logger.ts +196 -0
- package/react-native/src/sources/main.ts +61 -0
- package/react-native/src/sources/observer-manager.ts +185 -0
- package/react-native/src/sources/observer.ts +79 -0
- package/react-native/src/sources/pending-collections-operation.ts +241 -0
- package/react-native/src/sources/pending-cursor-operation.ts +218 -0
- package/react-native/src/sources/pending-id-specific-operation.ts +218 -0
- package/react-native/src/sources/presence-manager.ts +170 -0
- package/react-native/src/sources/presence.ts +427 -0
- package/react-native/src/sources/query-result-item.ts +131 -0
- package/react-native/src/sources/query-result.ts +55 -0
- package/react-native/src/sources/register.ts +95 -0
- package/react-native/src/sources/small-peer-info.ts +166 -0
- package/react-native/src/sources/static-tcp-client.ts +8 -0
- package/react-native/src/sources/store-observer.ts +170 -0
- package/react-native/src/sources/store.ts +630 -0
- package/react-native/src/sources/subscription-manager.ts +99 -0
- package/react-native/src/sources/subscription.ts +89 -0
- package/react-native/src/sources/sync-subscription.ts +90 -0
- package/react-native/src/sources/sync.ts +561 -0
- package/react-native/src/sources/test-helpers.ts +24 -0
- package/react-native/src/sources/transport-conditions-manager.ts +104 -0
- package/react-native/src/sources/transport-config.ts +430 -0
- package/react-native/src/sources/update-result.ts +66 -0
- package/react-native/src/sources/update-results-map.ts +65 -0
- package/react-native/src/sources/websocket-client.ts +7 -0
- package/react-native/src/sources/write-transaction-collection.ts +122 -0
- package/react-native/src/sources/write-transaction-pending-cursor-operation.ts +101 -0
- package/react-native/src/sources/write-transaction-pending-id-specific-operation.ts +74 -0
- package/react-native/src/sources/write-transaction.ts +121 -0
- package/react-native.config.js +9 -0
- package/web/ditto.es6.js +1 -1
- package/web/ditto.umd.js +1 -1
- package/web/ditto.wasm +0 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright © 2023 DittoLive Incorporated. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as FFI from './ffi'
|
|
6
|
+
|
|
7
|
+
import { Logger } from './logger'
|
|
8
|
+
|
|
9
|
+
import type { Document, MutableDocument } from './document'
|
|
10
|
+
import type { Attachment } from './attachment'
|
|
11
|
+
import type { ConnectionRequest } from './connection-request'
|
|
12
|
+
import type { QueryResult } from './query-result'
|
|
13
|
+
import type { QueryResultItem } from './query-result-item'
|
|
14
|
+
|
|
15
|
+
import type { StaticTCPClient } from './static-tcp-client'
|
|
16
|
+
import type { WebsocketClient } from './websocket-client'
|
|
17
|
+
|
|
18
|
+
import type { Ditto } from './ditto'
|
|
19
|
+
|
|
20
|
+
// Add bridged type name to debug, for example 'Ditto'.
|
|
21
|
+
const DEBUG_TYPE_NAMES: string[] = []
|
|
22
|
+
const DEBUG_ALL_TYPES = false
|
|
23
|
+
|
|
24
|
+
// -----------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A JavaScript class that identifies a bridge's type and may be used to create
|
|
28
|
+
* new instances.
|
|
29
|
+
*/
|
|
30
|
+
export type BridgeType<T extends object> = new (...args: any[]) => T
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A handle serves as a safe wrapper around a pointer to a native object.
|
|
34
|
+
*
|
|
35
|
+
* A bridge keeps track of all handles that have been created on its
|
|
36
|
+
* {@link Bridge.handlesByAddress | `handlesByAddress`} property, which allows
|
|
37
|
+
* enumerating all objects that are currently managed by the bridge.
|
|
38
|
+
*
|
|
39
|
+
* @internal */
|
|
40
|
+
export class Handle<T extends object, FFIType> {
|
|
41
|
+
readonly bridge: Bridge<T, FFIType>
|
|
42
|
+
readonly pointer: FFI.Pointer<FFIType>
|
|
43
|
+
|
|
44
|
+
private objectWeakRef: WeakRef<T>
|
|
45
|
+
|
|
46
|
+
readonly isClosed: boolean = false
|
|
47
|
+
readonly isFinalized: boolean = false
|
|
48
|
+
readonly isUnregistered: boolean = false
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Warning: Do not call this constructor directly. Use
|
|
52
|
+
* {@link Bridge.handleFor | `Bridge.<type>.handleFor()`} instead.
|
|
53
|
+
*
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
constructor(bridge: Bridge<T, FFIType>, object: T, pointer: FFI.Pointer<FFIType>) {
|
|
57
|
+
this.bridge = bridge
|
|
58
|
+
this.objectWeakRef = new WeakRef(object)
|
|
59
|
+
this.pointer = pointer
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The type of this handle's bridge */
|
|
63
|
+
get type(): BridgeType<T> {
|
|
64
|
+
return this.bridge.type
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns the pointer associated with this handle.
|
|
69
|
+
*
|
|
70
|
+
* @throws {Error} if the object has already been closed, garbage collected,
|
|
71
|
+
* or unregistered from the bridge.
|
|
72
|
+
*/
|
|
73
|
+
deref(): FFI.Pointer<FFIType> {
|
|
74
|
+
if (this.isClosed) {
|
|
75
|
+
throw new Error(`Bridging error: can't get pointer for an object that has been closed.`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.isFinalized) {
|
|
79
|
+
throw new Error(`Bridging error: can't get pointer for an object that has been finalized.`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (this.isUnregistered) {
|
|
83
|
+
throw new Error(`Bridging error: can't get pointer for an object that has been unregistered.`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return this.pointer
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns the pointer associated with this handle or `null` if the object
|
|
91
|
+
* has been closed, garbage collected, or unregistered.
|
|
92
|
+
*/
|
|
93
|
+
derefOrNull(): FFI.Pointer<FFIType> | null {
|
|
94
|
+
if (this.isClosed) {
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (this.isFinalized) {
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (this.isUnregistered) {
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return this.pointer ?? null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Returns the object associated with this handle.
|
|
111
|
+
*
|
|
112
|
+
* @throws {Error} if the object has been closed, unregistered, or garbage collected,
|
|
113
|
+
* closed or unregistered.
|
|
114
|
+
*/
|
|
115
|
+
object(): T {
|
|
116
|
+
const object = this.objectWeakRef.deref()
|
|
117
|
+
if (object == null) {
|
|
118
|
+
throw new Error(`Bridging error: ${this.bridge.type.name} object has been garbage collected.`)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (this.isClosed) {
|
|
122
|
+
throw new Error(`Bridging error: ${this.bridge.type.name} object has been closed.`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.isUnregistered) {
|
|
126
|
+
throw new Error(`Bridging error: ${this.bridge.type.name} object has been unregistered.`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return object
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Returns the object associated with this handle or `null` if the object
|
|
134
|
+
* has been closed, unregistered, or garbage collected.
|
|
135
|
+
*/
|
|
136
|
+
objectOrNull(): T | null {
|
|
137
|
+
return this.objectWeakRef.deref() ?? null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** @internal */
|
|
141
|
+
toString(): string {
|
|
142
|
+
const pointer = this.derefOrNull()
|
|
143
|
+
return `{ Handle | type: ${this.bridge.type}, object: ${this.objectWeakRef.deref()}, FFI address: ${pointer?.addr}, FFI type: ${pointer?.type} }`
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** @internal */
|
|
147
|
+
bridgeWillClose() {
|
|
148
|
+
// @ts-expect-error setting readonly property
|
|
149
|
+
this.isClosed = true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @internal */
|
|
153
|
+
bridgeDidClose() {
|
|
154
|
+
// @ts-expect-error setting readonly property
|
|
155
|
+
this.pointer = null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** @internal */
|
|
159
|
+
bridgeWillFinalize() {
|
|
160
|
+
// @ts-expect-error setting readonly property
|
|
161
|
+
this.isFinalized = true
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** @internal */
|
|
165
|
+
bridgeDidFinalize() {
|
|
166
|
+
// @ts-expect-error setting readonly property
|
|
167
|
+
this.pointer = null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** @internal */
|
|
171
|
+
bridgeWillUnregister() {
|
|
172
|
+
// @ts-expect-error setting readonly property
|
|
173
|
+
this.isUnregistered = true
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** @internal */
|
|
177
|
+
bridgeDidUnregister() {
|
|
178
|
+
// @ts-expect-error setting readonly property
|
|
179
|
+
this.pointer = null
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Use this for passing arrays of pointers to the FFI.
|
|
185
|
+
*/
|
|
186
|
+
export class Handles<T extends object, FFIType> {
|
|
187
|
+
readonly handles: Handle<T, FFIType>[]
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @throws {Error} if any of the objects are not registered in the bridge.
|
|
191
|
+
* @throws {Error} if any of the objects have already been garbage collected.
|
|
192
|
+
*/
|
|
193
|
+
constructor(bridge: Bridge<T, FFIType>, objects: T[]) {
|
|
194
|
+
this.handles = objects.map((object) => bridge.handleFor(object))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
deref(): FFI.Pointer<FFIType>[] {
|
|
198
|
+
return this.handles.map((handle) => handle.deref())
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* A bridge manages memory allocated by the FFI that is used in the JS SDK.
|
|
204
|
+
*
|
|
205
|
+
* The main purpose of a bridge is keeping track of JavaScript objects that
|
|
206
|
+
* require access to memory allocated by the FFI. When such objects are
|
|
207
|
+
* garbage collected in JavaScript, the bridge instructs the FFI to free the
|
|
208
|
+
* corresponding memory. Every managed memory pointer corresponds to exactly
|
|
209
|
+
* one JS Object.
|
|
210
|
+
*
|
|
211
|
+
* There is a static `Bridge` instance for every class of objects that can be
|
|
212
|
+
* managed:
|
|
213
|
+
*
|
|
214
|
+
* - {@link Attachment}: `Bridge.attachment`
|
|
215
|
+
* - {@link Ditto}: `Bridge.ditto`
|
|
216
|
+
* - {@link Document}: `Bridge.document`
|
|
217
|
+
* - {@link MutableDocument}: `Bridge.mutableDocument`
|
|
218
|
+
* - {@link StaticTCPClient}: `Bridge.staticTCPClient`
|
|
219
|
+
* - {@link WebsocketClient}: `Bridge.websocketClient`
|
|
220
|
+
*
|
|
221
|
+
* Use `Bridge.<type>.handleFor()` to obtain a handle, which is a wrapper around
|
|
222
|
+
* the raw pointer, and `Bridge.<type>.bridge()` to get or create the matching
|
|
223
|
+
* object for a memory address.
|
|
224
|
+
*
|
|
225
|
+
* @internal */
|
|
226
|
+
export class Bridge<T extends object, FFIType> {
|
|
227
|
+
readonly release: (pointer: FFI.Pointer<FFIType>) => void | Promise<void>
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Creates a new bridge for objects of `type`. Requires a `release` function
|
|
231
|
+
* that is called whenever a registered object is garbage collected, passing
|
|
232
|
+
* the associated `pointer` to it. The release function is then responsible
|
|
233
|
+
* to free or drop the corresponding native object.
|
|
234
|
+
*
|
|
235
|
+
* **IMPORTANT**: The `type` of all bridges needs to be set in `epilogue.ts`
|
|
236
|
+
* after initiating the bridge instance. This helps avoid import cycles
|
|
237
|
+
* (otherwise anything importing the bridge instance, would also have to
|
|
238
|
+
* import the type, which usually leads to import cycles).
|
|
239
|
+
*
|
|
240
|
+
* @private
|
|
241
|
+
*/
|
|
242
|
+
constructor(release: (pointer: FFI.Pointer<FFIType>) => void | Promise<void>) {
|
|
243
|
+
this.release = release
|
|
244
|
+
this.handlesByAddress = {}
|
|
245
|
+
this.handlesByObject = new WeakMap()
|
|
246
|
+
this.finalizationRegistry = new FinalizationRegistry(this.finalize.bind(this))
|
|
247
|
+
|
|
248
|
+
Bridge.all.push(new WeakRef(this))
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The type of a bridge is the JavaScript `Class` of objects it represents.
|
|
253
|
+
*
|
|
254
|
+
* @internal */
|
|
255
|
+
get type(): BridgeType<T> {
|
|
256
|
+
if (this.internalType == null) {
|
|
257
|
+
throw new Error('Bridge type has not been registered yet.')
|
|
258
|
+
}
|
|
259
|
+
return this.internalType
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* All bridges' types have to be registered in `epilogue.ts` before using
|
|
264
|
+
* them.
|
|
265
|
+
*
|
|
266
|
+
* @internal */
|
|
267
|
+
registerType(value: BridgeType<T>) {
|
|
268
|
+
if (this.internalType === value) {
|
|
269
|
+
// Nothing to do.
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (this.internalType) {
|
|
274
|
+
throw new Error(`Can't register bridged type '${value.name}', another type was already registered: ${this.internalType}`)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.internalType = value
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Returns the handle for a bridged object.
|
|
282
|
+
*
|
|
283
|
+
* Use `handle.deref()` to get the pointer for the object at the time of use.
|
|
284
|
+
*
|
|
285
|
+
* @throws {Error} if the object is not registered.
|
|
286
|
+
*
|
|
287
|
+
* @internal
|
|
288
|
+
*/
|
|
289
|
+
handleFor(object: T): Handle<T, FFIType> {
|
|
290
|
+
const handle = this.handlesByObject.get(object)
|
|
291
|
+
if (handle == null) {
|
|
292
|
+
throw new Error(`Bridging error: ${this.type.name} object is not currently registered in this bridge.`)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return handle
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Returns a `Handles` instance for an array of objects.
|
|
300
|
+
*
|
|
301
|
+
* @internal
|
|
302
|
+
*/
|
|
303
|
+
handlesFor(objects: T[]): Handles<T, FFIType> {
|
|
304
|
+
return new Handles(this, objects)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Convenience method, returns the object for the FFI `pointer` if registered,
|
|
309
|
+
* otherwise returns `undefined`. If the object associated with the `pointer`
|
|
310
|
+
* has been unregistered before, returns `undefined`, too.
|
|
311
|
+
*
|
|
312
|
+
* @internal
|
|
313
|
+
*/
|
|
314
|
+
objectFor(pointer: FFI.Pointer<FFIType>): T | undefined {
|
|
315
|
+
const handle = this.handlesByAddress[pointer.addr]
|
|
316
|
+
if (!handle) return undefined
|
|
317
|
+
if (handle.type !== this.type) throw new Error(`Can't return object for pointer, pointer is associated with an object of type ${handle.type} but this bridge is configured for ${this.type}`)
|
|
318
|
+
|
|
319
|
+
// This throws an error if the object has been garbage collected but the
|
|
320
|
+
// finalizer has not been called yet.
|
|
321
|
+
return handle.object()
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Returns the object for the FFI `pointer` if registered. Otherwise, calls
|
|
326
|
+
* the passed in `create` function to create a new object, which it then
|
|
327
|
+
* returns after registering. If no `create` function is given, uses the
|
|
328
|
+
* type of the bridge as a constructor and creates a new instance of it
|
|
329
|
+
* without passing any parameters.
|
|
330
|
+
*
|
|
331
|
+
* @param pointer reference to the FFi instance for the object
|
|
332
|
+
* @param objectOrCreate can either be the JS object, or a function that returns the instance when called. If undefined, an object is created based on the Bridge type.
|
|
333
|
+
* @throws {Error} if `objectOrCreate` is a function that returns an object that is not an instance of the bridge's type.
|
|
334
|
+
* @internal
|
|
335
|
+
*/
|
|
336
|
+
bridge(pointer: FFI.Pointer<FFIType>, objectOrCreate?: T | (() => T)): T {
|
|
337
|
+
const existingObject = this.objectFor(pointer)
|
|
338
|
+
if (existingObject) {
|
|
339
|
+
return existingObject
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!objectOrCreate) {
|
|
343
|
+
objectOrCreate = () => {
|
|
344
|
+
return Reflect.construct(this.type, [])
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
let object: T
|
|
349
|
+
if (typeof objectOrCreate === 'function') {
|
|
350
|
+
object = objectOrCreate()
|
|
351
|
+
if (!(object instanceof this.type)) {
|
|
352
|
+
throw new Error(`Can't bridge, expected passed in create function to return a ${this.type.name} object but got: ${object}`)
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
object = objectOrCreate
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
this.register(object, pointer)
|
|
359
|
+
return object
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Registers an instance in this bridge's {@link FinalizationRegistry}.
|
|
364
|
+
*
|
|
365
|
+
* This causes the FFI to drop the memory linked to the object as soon as it
|
|
366
|
+
* is garbage collected in JavaScript.
|
|
367
|
+
*
|
|
368
|
+
* If you want to control the order with which a number of objects' memory is
|
|
369
|
+
* dropped, use {@link Bridge.unregister | Bridge.unregister()}
|
|
370
|
+
*
|
|
371
|
+
* @private */
|
|
372
|
+
register(object: T, pointer: FFI.Pointer<FFIType>) {
|
|
373
|
+
const objectType = object.constructor as BridgeType<T>
|
|
374
|
+
if (objectType !== this.type) throw new Error(`Can't register, bridge is configured for type ${this.type.name} but passed in object is of type ${objectType.name}`)
|
|
375
|
+
|
|
376
|
+
const existingHandle = this.handlesByObject.get(object)
|
|
377
|
+
const existingPointer = existingHandle ? existingHandle.pointer : null
|
|
378
|
+
|
|
379
|
+
// Check that both pointer and handle are undefined at this point.
|
|
380
|
+
if (existingPointer != null && existingHandle != null) throw new Error(`Can't register, an object for the passed in pointer has previously been registered: ${existingHandle.object()}`)
|
|
381
|
+
|
|
382
|
+
if (existingPointer != null && existingHandle == null) throw new Error(`Internal inconsistency, trying to register an object which has an associated pointer but no handle entry: ${objectType.name} at ${existingPointer.type} ${existingPointer.addr}`)
|
|
383
|
+
if (existingPointer == null && existingHandle != null) throw new Error(`Internal inconsistency, trying to register an object which has a handle entry but no associated pointer: ${objectType.name} ${object}`)
|
|
384
|
+
|
|
385
|
+
const handle = new Handle<T, FFIType>(this, object, pointer)
|
|
386
|
+
this.handlesByAddress[pointer.addr] = handle
|
|
387
|
+
this.handlesByObject.set(object, handle)
|
|
388
|
+
|
|
389
|
+
this.finalizationRegistry.register(object as object, handle, object as object)
|
|
390
|
+
|
|
391
|
+
if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
|
|
392
|
+
Logger.debug(`[VERBOSE] Bridge REGISTERED a new instance of ${this.type.name}, current count: ${Object.keys(this.handlesByAddress).length}`)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Removes an instance from this bridge's {@link FinalizationRegistry}.
|
|
398
|
+
*
|
|
399
|
+
* This lets you control the order with which memory is dropped in the FFI.
|
|
400
|
+
* After calling this function, manually call the FFI function to drop the
|
|
401
|
+
* memory then finally delete the JavaScript instance.
|
|
402
|
+
*
|
|
403
|
+
* @internal */
|
|
404
|
+
unregister(object: T) {
|
|
405
|
+
const objectType = object.constructor
|
|
406
|
+
const bridgeType = this.type
|
|
407
|
+
if (objectType !== bridgeType) throw new Error(`Can't unregister, bridge is configured for type ${bridgeType.name} but passed in object is of type ${objectType.name}`)
|
|
408
|
+
|
|
409
|
+
const handle = this.handlesByObject.get(object)
|
|
410
|
+
if (handle == null) throw new Error(`Can't unregister, object has not been registered before: ${object}`)
|
|
411
|
+
if (handle.type !== bridgeType) throw new Error(`Internal inconsistency, trying to unregister an object that has a handle with a different type than that of the bridge: ${handle}`)
|
|
412
|
+
if (handle.objectOrNull() !== object) throw new Error(`Internal inconsistency, trying to unregister an object whose associated handle holds a different object: ${handle}`)
|
|
413
|
+
|
|
414
|
+
if (handle.isClosed) throw new Error(`Can't unregister, object has been closed before: ${object}`)
|
|
415
|
+
if (handle.isFinalized) throw new Error(`Can't unregister, object has been finalized before: ${object}`)
|
|
416
|
+
if (handle.isUnregistered) throw new Error(`Can't unregister, object has been unregistered already: ${object}`)
|
|
417
|
+
|
|
418
|
+
handle.bridgeWillUnregister()
|
|
419
|
+
this.finalizationRegistry.unregister(object as object)
|
|
420
|
+
delete this.handlesByAddress[handle.pointer.addr]
|
|
421
|
+
this.handlesByObject.delete(object)
|
|
422
|
+
handle.bridgeDidUnregister()
|
|
423
|
+
|
|
424
|
+
if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
|
|
425
|
+
Logger.debug(`[VERBOSE] Bridge UNREGISTERED an instance of ${this.type.name}, current count: ${Object.keys(this.handlesByAddress).length}`)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** @internal */
|
|
430
|
+
unregisterAll() {
|
|
431
|
+
if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
|
|
432
|
+
Logger.debug(`[VERBOSE] Unregistering ALL bridged instances of type ${this.type.name}.`)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
for (const handle of Object.values(this.handlesByAddress)) {
|
|
436
|
+
const object = handle.object()
|
|
437
|
+
if (object) {
|
|
438
|
+
this.unregister(object)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Closes the object by calling `release()` and `null`-ing its pointer, such
|
|
445
|
+
* that its handle can't be `deref()`-ed afterwards.
|
|
446
|
+
*
|
|
447
|
+
* @internal */
|
|
448
|
+
async close(object: T): Promise<void> {
|
|
449
|
+
const objectType = object.constructor
|
|
450
|
+
const bridgeType = this.type
|
|
451
|
+
if (objectType !== bridgeType) throw new Error(`Can't close, bridge is configured for type ${bridgeType.name} but passed in object is of type ${objectType.name}`)
|
|
452
|
+
|
|
453
|
+
const handle = this.handlesByObject.get(object)
|
|
454
|
+
if (handle == null) throw new Error(`Can't close an object that has not been registered before: ${object}`)
|
|
455
|
+
if (handle.type !== bridgeType) throw new Error(`Internal inconsistency, trying to close an object that has a handle with a different type than that of the bridge: ${handle}`)
|
|
456
|
+
|
|
457
|
+
if (handle.isUnregistered) throw new Error(`Can't close object, object has been unregistered.`)
|
|
458
|
+
if (handle.isFinalized) throw new Error(`Internal inconsistency, trying to close an object that has already been finalized.`)
|
|
459
|
+
if (handle.isClosed) return
|
|
460
|
+
|
|
461
|
+
const pointer = handle.pointer
|
|
462
|
+
if (!pointer) throw new Error(`Internal inconsistency, trying to close an object whose pointer is null.`)
|
|
463
|
+
|
|
464
|
+
handle.bridgeWillClose()
|
|
465
|
+
delete this.handlesByAddress[pointer.addr]
|
|
466
|
+
await this.release(pointer)
|
|
467
|
+
handle.bridgeDidClose()
|
|
468
|
+
|
|
469
|
+
if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
|
|
470
|
+
Logger.debug(`[VERBOSE] Bridge CLOSED an instance of ${this.type.name}, current count: ${Object.keys(this.handlesByAddress).length}`)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** @internal */
|
|
475
|
+
get count(): number {
|
|
476
|
+
return Object.keys(this.handlesByAddress).length
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Keeps track of all bridges for debugging and test purposes. With this, we
|
|
481
|
+
* can iterate over all bridges and make sure everything is deallocated after
|
|
482
|
+
* a test, or a suite of tests, has run.
|
|
483
|
+
*
|
|
484
|
+
* @internal */
|
|
485
|
+
static readonly all: Array<WeakRef<Bridge<any, any>>> = []
|
|
486
|
+
|
|
487
|
+
/** @internal */
|
|
488
|
+
static readonly attachment = new Bridge<Attachment, FFI.AttachmentHandle>(FFI.freeAttachmentHandle)
|
|
489
|
+
|
|
490
|
+
/** @internal */
|
|
491
|
+
static readonly connectionRequest = new Bridge<ConnectionRequest, FFI.FFIConnectionRequest>(FFI.connectionRequestFree)
|
|
492
|
+
|
|
493
|
+
/** @internal */
|
|
494
|
+
static readonly document = new Bridge<Document, FFI.FFIDocument>(FFI.documentFree)
|
|
495
|
+
|
|
496
|
+
/** @internal */
|
|
497
|
+
static readonly mutableDocument = new Bridge<MutableDocument, FFI.FFIDocument>(FFI.documentFree)
|
|
498
|
+
|
|
499
|
+
/** @internal */
|
|
500
|
+
static readonly queryResult = new Bridge<QueryResult, FFI.FFIQueryResult>(FFI.queryResultFree)
|
|
501
|
+
|
|
502
|
+
/** @internal */
|
|
503
|
+
static readonly queryResultItem = new Bridge<QueryResultItem, FFI.FFIQueryResultItem>(FFI.queryResultItemFree)
|
|
504
|
+
|
|
505
|
+
/** @internal */
|
|
506
|
+
static readonly staticTCPClient = new Bridge<StaticTCPClient, FFI.FFIStaticTCPClient>(FFI.staticTCPClientFreeHandle)
|
|
507
|
+
|
|
508
|
+
/** @internal */
|
|
509
|
+
static readonly websocketClient = new Bridge<WebsocketClient, FFI.FFIWebsocketClient>(FFI.websocketClientFreeHandle)
|
|
510
|
+
|
|
511
|
+
/** @internal */
|
|
512
|
+
static readonly ditto = new Bridge<Ditto, FFI.FFIDitto>(async (dittoPointer) => {
|
|
513
|
+
await FFI.dittoClearPresenceCallback(dittoPointer)
|
|
514
|
+
await FFI.dittoShutdown(dittoPointer)
|
|
515
|
+
FFI.dittoFree(dittoPointer)
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
// ------ Private ------
|
|
519
|
+
|
|
520
|
+
private internalType: BridgeType<T> | null = null
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* All bridged objects' {@link Handle} entries for lookup by pointer address.
|
|
524
|
+
*/
|
|
525
|
+
private handlesByAddress: { [key: FFI.Pointer<T>['addr']]: Handle<T, FFIType> }
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Look up a handle given its object.
|
|
529
|
+
*
|
|
530
|
+
* As `WeakMap` does not allow for enumeration, use `this.handlesByAddress` to
|
|
531
|
+
* iterate over all handles.
|
|
532
|
+
*/
|
|
533
|
+
private handlesByObject: WeakMap<T, Handle<T, FFIType>>
|
|
534
|
+
|
|
535
|
+
private finalizationRegistry: FinalizationRegistry<any>
|
|
536
|
+
|
|
537
|
+
private async finalize(handle: Handle<T, FFIType>): Promise<void> {
|
|
538
|
+
if (handle.isFinalized) throw new Error(`Internal inconsistency, trying to finalize an object that has already been finalized.`)
|
|
539
|
+
if (handle.isUnregistered) throw new Error(`Internal inconsistency, trying to finalize an object that has been unregistered before.`)
|
|
540
|
+
|
|
541
|
+
handle.bridgeWillFinalize()
|
|
542
|
+
|
|
543
|
+
if (!handle.isClosed) {
|
|
544
|
+
const pointer = handle.pointer
|
|
545
|
+
if (!pointer) throw new Error(`Internal inconsistency, trying to finalize an object whose pointer is null.`)
|
|
546
|
+
|
|
547
|
+
delete this.handlesByAddress[pointer.addr]
|
|
548
|
+
await this.release(pointer)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
handle.bridgeDidFinalize()
|
|
552
|
+
|
|
553
|
+
if (DEBUG_TYPE_NAMES.includes(this.type.name) || DEBUG_ALL_TYPES) {
|
|
554
|
+
Logger.debug(`[VERBOSE] Bridge FINALIZED an instance of ${this.type.name}, current count: ${Object.keys(this.handlesByAddress).length}`)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// NOTE: this is patched up with the actual build version by Jake task
|
|
2
|
+
// build:package and has to be a valid semantic version as defined here: https://semver.org.
|
|
3
|
+
export const fullBuildVersionString = '{full-build-version-string}'
|
|
4
|
+
|
|
5
|
+
// NOTE: this is patched up with the default URL for the ditto.wasm by Jake task
|
|
6
|
+
// build:package. Usually it looks something like this:
|
|
7
|
+
// https://software.ditto.live/js/Ditto/1.2.3-alpha.456/ditto.wasm
|
|
8
|
+
export const defaultDittoWasmFileURL = '{default-ditto-wasm-file-url}'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright © 2021 DittoLive Incorporated. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { CBOR as CBORRedux } from './@cbor-redux'
|
|
6
|
+
|
|
7
|
+
/** @internal */
|
|
8
|
+
export class CBOR {
|
|
9
|
+
/** @internal */
|
|
10
|
+
static encode(data: any, replacer?: (key: any, value: any) => any): Uint8Array {
|
|
11
|
+
const arrayBuffer = CBORRedux.encode(data, replacer)
|
|
12
|
+
return new Uint8Array(arrayBuffer)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** @internal */
|
|
16
|
+
static decode(data: Uint8Array, reviver?: (key: any, value: any) => any): any {
|
|
17
|
+
const arrayBuffer = data.buffer
|
|
18
|
+
return CBORRedux.decode(arrayBuffer, reviver)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright © 2023 DittoLive Incorporated. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { DocumentValue } from './document'
|
|
6
|
+
import type { DocumentID, DocumentIDValue } from './document-id'
|
|
7
|
+
import type { WriteStrategy, QueryArguments } from './essentials'
|
|
8
|
+
import type { Store } from './store'
|
|
9
|
+
import type { BasePendingCursorOperation } from './base-pending-cursor-operation'
|
|
10
|
+
import type { BasePendingIDSpecificOperation } from './base-pending-id-specific-operation'
|
|
11
|
+
|
|
12
|
+
export type UpsertOptions = {
|
|
13
|
+
/**
|
|
14
|
+
* Specifies the desired strategy for inserting a document. The default
|
|
15
|
+
* strategy is `merge`. See {@link WriteStrategy} for more information.
|
|
16
|
+
*/
|
|
17
|
+
writeStrategy?: WriteStrategy
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CollectionInterface {
|
|
21
|
+
/** The name of the collection. */
|
|
22
|
+
readonly name: string
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The store this collection belongs to.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
readonly store: Store
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Search for documents in this collection using the provided query string.
|
|
32
|
+
*
|
|
33
|
+
* @param query The query to run against the collection.
|
|
34
|
+
* @param queryArgs These arguments replace placeholders in the provided
|
|
35
|
+
* query.
|
|
36
|
+
*/
|
|
37
|
+
find(query: string, queryArgs?: QueryArguments): BasePendingCursorOperation
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convenience method, equivalent to calling {@link find | find()} and passing
|
|
41
|
+
* the query `"true"`.
|
|
42
|
+
*/
|
|
43
|
+
findAll(): BasePendingCursorOperation
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Find documents given a specific ID.
|
|
47
|
+
*
|
|
48
|
+
* Use the returned cursor instance to chain operations on the search result.
|
|
49
|
+
*
|
|
50
|
+
* @param id The document's identifier
|
|
51
|
+
*/
|
|
52
|
+
findByID(id: DocumentID | DocumentIDValue): BasePendingIDSpecificOperation
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* TEMPORARY: helper to deal with non-canonical IDs.
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
findByIDCBOR(idCBOR: Uint8Array): BasePendingIDSpecificOperation
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Inserts a new document into the collection and returns its ID.
|
|
63
|
+
*
|
|
64
|
+
* If the document already exists, the contents of both are merged by default.
|
|
65
|
+
* You can change this by providing a different `writeStrategy` via `options`.
|
|
66
|
+
*
|
|
67
|
+
* @param value The content of the document to be inserted or updated. Must
|
|
68
|
+
* not contain any non-finite numbers (NaN, Infinity, -Infinity).
|
|
69
|
+
* @param options Change defaults for the behavior of the operation, such as
|
|
70
|
+
* the write strategy.
|
|
71
|
+
*/
|
|
72
|
+
upsert(value: DocumentValue, options: UpsertOptions): Promise<DocumentID>
|
|
73
|
+
}
|