@dittolive/ditto 4.7.4 → 4.8.0-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 (126) hide show
  1. package/DittoReactNative.podspec +33 -2
  2. package/README.md +2 -2
  3. package/node/ditto.cjs.js +341 -376
  4. package/node/ditto.darwin-arm64.node +0 -0
  5. package/node/ditto.darwin-x64.node +0 -0
  6. package/node/ditto.linux-arm.node +0 -0
  7. package/node/ditto.linux-arm64.node +0 -0
  8. package/node/ditto.linux-x64.node +0 -0
  9. package/node/ditto.win32-x64.node +0 -0
  10. package/package.json +2 -2
  11. package/react-native/android/build.gradle +24 -64
  12. package/react-native/android/cpp-adapter.cpp +37 -104
  13. package/react-native/android/src/main/AndroidManifestNew.xml +2 -0
  14. package/react-native/android/src/main/java/com/dittolive/rnsdk/DittoRNSDKModule.java +5 -70
  15. package/react-native/cpp/include/ConnectionRequest.h +1 -1
  16. package/react-native/cpp/include/IO.h +2 -0
  17. package/react-native/cpp/include/Logger.h +2 -1
  18. package/react-native/cpp/include/Misc.h +1 -0
  19. package/react-native/cpp/include/main.h +4 -2
  20. package/react-native/cpp/src/Attachment.cpp +1 -3
  21. package/react-native/cpp/src/ConnectionRequest.cpp +1 -1
  22. package/react-native/cpp/src/IO.cpp +79 -0
  23. package/react-native/cpp/src/Logger.cpp +63 -0
  24. package/react-native/cpp/src/Misc.cpp +21 -0
  25. package/react-native/cpp/src/main.cpp +10 -4
  26. package/react-native/ditto.es6.js +2 -0
  27. package/react-native/dittoffi/dittoffi.h +137 -40
  28. package/react-native/ios/DittoRNSDK.mm +19 -124
  29. package/react-native.config.js +2 -1
  30. package/types/ditto.d.ts +2412 -2032
  31. package/web/ditto.es6.js +1 -1
  32. package/web/ditto.umd.js +1 -1
  33. package/web/ditto.wasm +0 -0
  34. package/node/transports.darwin-arm64.node +0 -0
  35. package/node/transports.darwin-x64.node +0 -0
  36. package/react-native/android/.gradle/8.9/checksums/checksums.lock +0 -0
  37. package/react-native/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  38. package/react-native/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  39. package/react-native/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  40. package/react-native/android/.gradle/8.9/gc.properties +0 -0
  41. package/react-native/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  42. package/react-native/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  43. package/react-native/android/.gradle/vcs-1/gc.properties +0 -0
  44. package/react-native/lib/commonjs/ditto.rn.js +0 -93
  45. package/react-native/lib/commonjs/ditto.rn.js.map +0 -1
  46. package/react-native/lib/commonjs/index.js +0 -61
  47. package/react-native/lib/commonjs/index.js.map +0 -1
  48. package/react-native/lib/module/ditto.rn.js +0 -89
  49. package/react-native/lib/module/ditto.rn.js.map +0 -1
  50. package/react-native/lib/module/index.js +0 -27
  51. package/react-native/lib/module/index.js.map +0 -1
  52. package/react-native/lib/typescript/ditto.rn.d.ts +0 -15
  53. package/react-native/lib/typescript/ditto.rn.d.ts.map +0 -1
  54. package/react-native/lib/typescript/index.d.ts +0 -1
  55. package/react-native/lib/typescript/index.d.ts.map +0 -1
  56. package/react-native/src/ditto.rn.ts +0 -123
  57. package/react-native/src/environment/environment.fallback.ts +0 -4
  58. package/react-native/src/index.ts +0 -29
  59. package/react-native/src/sources/@cbor-redux.ts +0 -2
  60. package/react-native/src/sources/@ditto.core.ts +0 -1
  61. package/react-native/src/sources/@environment.ts +0 -1
  62. package/react-native/src/sources/attachment-fetch-event.ts +0 -54
  63. package/react-native/src/sources/attachment-fetcher-manager.ts +0 -145
  64. package/react-native/src/sources/attachment-fetcher.ts +0 -265
  65. package/react-native/src/sources/attachment-token.ts +0 -129
  66. package/react-native/src/sources/attachment.ts +0 -121
  67. package/react-native/src/sources/augment.ts +0 -108
  68. package/react-native/src/sources/authenticator.ts +0 -314
  69. package/react-native/src/sources/base-pending-cursor-operation.ts +0 -255
  70. package/react-native/src/sources/base-pending-id-specific-operation.ts +0 -112
  71. package/react-native/src/sources/bridge.ts +0 -557
  72. package/react-native/src/sources/build-time-constants.ts +0 -8
  73. package/react-native/src/sources/cbor.ts +0 -20
  74. package/react-native/src/sources/collection-interface.ts +0 -73
  75. package/react-native/src/sources/collection.ts +0 -219
  76. package/react-native/src/sources/collections-event.ts +0 -99
  77. package/react-native/src/sources/connection-request.ts +0 -142
  78. package/react-native/src/sources/counter.ts +0 -82
  79. package/react-native/src/sources/ditto.ts +0 -991
  80. package/react-native/src/sources/document-id.ts +0 -163
  81. package/react-native/src/sources/document-path.ts +0 -308
  82. package/react-native/src/sources/document.ts +0 -237
  83. package/react-native/src/sources/epilogue.ts +0 -32
  84. package/react-native/src/sources/error-codes.ts +0 -114
  85. package/react-native/src/sources/error.ts +0 -256
  86. package/react-native/src/sources/essentials.ts +0 -81
  87. package/react-native/src/sources/ffi-error.ts +0 -134
  88. package/react-native/src/sources/ffi.ts +0 -2190
  89. package/react-native/src/sources/identity.ts +0 -163
  90. package/react-native/src/sources/init.ts +0 -71
  91. package/react-native/src/sources/internal.ts +0 -143
  92. package/react-native/src/sources/keep-alive.ts +0 -73
  93. package/react-native/src/sources/key-path.ts +0 -198
  94. package/react-native/src/sources/live-query-event.ts +0 -208
  95. package/react-native/src/sources/live-query-manager.ts +0 -110
  96. package/react-native/src/sources/live-query.ts +0 -167
  97. package/react-native/src/sources/logger.ts +0 -196
  98. package/react-native/src/sources/main.ts +0 -61
  99. package/react-native/src/sources/observer-manager.ts +0 -185
  100. package/react-native/src/sources/observer.ts +0 -79
  101. package/react-native/src/sources/pending-collections-operation.ts +0 -241
  102. package/react-native/src/sources/pending-cursor-operation.ts +0 -218
  103. package/react-native/src/sources/pending-id-specific-operation.ts +0 -218
  104. package/react-native/src/sources/presence-manager.ts +0 -170
  105. package/react-native/src/sources/presence.ts +0 -427
  106. package/react-native/src/sources/query-result-item.ts +0 -131
  107. package/react-native/src/sources/query-result.ts +0 -55
  108. package/react-native/src/sources/register.ts +0 -95
  109. package/react-native/src/sources/small-peer-info.ts +0 -166
  110. package/react-native/src/sources/static-tcp-client.ts +0 -8
  111. package/react-native/src/sources/store-observer.ts +0 -170
  112. package/react-native/src/sources/store.ts +0 -630
  113. package/react-native/src/sources/subscription-manager.ts +0 -99
  114. package/react-native/src/sources/subscription.ts +0 -89
  115. package/react-native/src/sources/sync-subscription.ts +0 -90
  116. package/react-native/src/sources/sync.ts +0 -561
  117. package/react-native/src/sources/test-helpers.ts +0 -24
  118. package/react-native/src/sources/transport-conditions-manager.ts +0 -104
  119. package/react-native/src/sources/transport-config.ts +0 -430
  120. package/react-native/src/sources/update-result.ts +0 -66
  121. package/react-native/src/sources/update-results-map.ts +0 -65
  122. package/react-native/src/sources/websocket-client.ts +0 -7
  123. package/react-native/src/sources/write-transaction-collection.ts +0 -122
  124. package/react-native/src/sources/write-transaction-pending-cursor-operation.ts +0 -101
  125. package/react-native/src/sources/write-transaction-pending-id-specific-operation.ts +0 -74
  126. package/react-native/src/sources/write-transaction.ts +0 -121
@@ -1,163 +0,0 @@
1
- //
2
- // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
- //
4
-
5
- import type { AuthenticationHandler } from './authenticator'
6
-
7
- /**
8
- * An identity to be used while in development when you want to control the app
9
- * name and the site ID of the peer.
10
- */
11
- export interface IdentityOfflinePlayground {
12
- type: 'offlinePlayground'
13
-
14
- /**
15
- * The app ID for the Ditto instance.
16
- */
17
- appID: string
18
-
19
- /**
20
- * The `siteID` needs to be an unsigned 64 bit integer >= 0, i.e. either a
21
- * regular non-fractional `number` or a `BigInt` in the range between 0 and
22
- * 2^64 - 1 (inclusive). If `siteID` is `0`, Ditto will generate an
23
- * appropriate site ID and persist it when needed. Default is `0`.
24
- */
25
- siteID?: number | BigInt
26
- }
27
-
28
- /**
29
- * An identity with intermediate level of security where all users and devices
30
- * are trusted and a single shared secret (key) between all peers satisfies the
31
- * security requirements.
32
- *
33
- * **NOTE**: This identity type is only supported for Node environments, using
34
- * this to create a Ditto instance in the web browser will throw an exception.
35
- */
36
- export interface IdentitySharedKey {
37
- type: 'sharedKey'
38
-
39
- /**
40
- * The app ID for the Ditto instance.
41
- */
42
- appID: string
43
-
44
- /**
45
- * The `siteID` needs to be an unsigned 64 bit integer >= 0, i.e. either a
46
- * regular non-fractional `number` or a `BigInt` in the range between 0 and
47
- * 2^64 - 1 (inclusive). If `siteID` is `0`, Ditto will generate an
48
- * appropriate site ID and persist it when needed. Default is `0`.
49
- */
50
- siteID?: number | BigInt
51
-
52
- /**
53
- * A base64 encoded DER representation of a private key, which is shared
54
- * between devices for a single app.
55
- */
56
- sharedKey: string
57
- }
58
-
59
- /**
60
- * A manually-provided certificate identity. This accepts a
61
- * base64-encoded bundle.
62
- *
63
- * A manual identity's `appID` is encoded in its certificate.
64
- */
65
- export interface IdentityManual {
66
- type: 'manual'
67
- certificate: string
68
- }
69
-
70
- /**
71
- * Test a Ditto Cloud app with weak shared token authentication ("Playground
72
- * mode"). This mode is not secure and must only be used for development.
73
- */
74
- export interface IdentityOnlinePlayground {
75
- type: 'onlinePlayground'
76
-
77
- /**
78
- * An ID identifying this app registration on the Ditto portal, which can be
79
- * found at https://portal.ditto.live.
80
- */
81
- appID: string
82
-
83
- /**
84
- * A shared token used to set up the OnlinePlayground session. This token is
85
- * provided by the Ditto Portal when setting up the application.
86
- */
87
- token: string
88
-
89
- /** If true, auto-configure sync with Ditto Cloud. Default is `true`. */
90
- enableDittoCloudSync?: boolean
91
-
92
- /**
93
- * If specified, use a custom authentication service instead of Ditto Cloud.
94
- * @internal
95
- */
96
- customAuthURL?: string
97
-
98
- /**
99
- * A custom Ditto Cloud URL.
100
- * @internal
101
- */
102
- customDittoCloudURL?: string
103
- }
104
-
105
- /**
106
- * Run Ditto in secure production mode, logging on to Ditto Cloud or an
107
- * on-premises authentication server. User permissions are centrally managed.
108
- * Sync will not work until a successful login has occurred.
109
- *
110
- * The only required configuration is the application's UUID, which can be found
111
- * on the Ditto portal where the app is registered.
112
- *
113
- * By default cloud sync is enabled. This means the SDK will sync to a Big Peer
114
- * in Ditto's cloud when an internet connection is available. This is controlled
115
- * by the `enableCloudSync` parameter. If `true` (default), a suitable wss://
116
- * URL will be added to the `TransportConfig`. To prevent cloud sync, or to
117
- * specify your own URL later, pass `false`.
118
- *
119
- * Authentication requests are handled by Ditto's cloud by default, configured
120
- * in the portal at https://portal.ditto.live.
121
- *
122
- * To use a different or on-premises authentication service, pass a custom HTTPS
123
- * base URL as the `customAuthUrl` parameter.
124
- */
125
- export interface IdentityOnlineWithAuthentication {
126
- type: 'onlineWithAuthentication'
127
-
128
- /**
129
- * An ID identifying this app registration on the Ditto portal, which can be
130
- * found at https://portal.ditto.live.
131
- */
132
- appID: string
133
-
134
- /**
135
- * If true, auto-configure sync with Ditto Cloud. Default is `true`.
136
- */
137
- enableDittoCloudSync?: boolean
138
-
139
- /**
140
- * A handler for when Ditto requires (re)authentication.
141
- */
142
- authHandler: AuthenticationHandler
143
-
144
- /**
145
- * If specified, use a custom authentication service instead of Ditto Cloud.
146
- */
147
- customAuthURL?: string
148
-
149
- /**
150
- * A custom Ditto Cloud URL.
151
- * @internal
152
- */
153
- customDittoCloudURL?: string
154
- }
155
-
156
- /**
157
- * The various identity configurations that you can use when initializing a
158
- * `Ditto` instance.
159
- */
160
- export type Identity = IdentityOfflinePlayground | IdentitySharedKey | IdentityManual | IdentityOnlinePlayground | IdentityOnlineWithAuthentication
161
-
162
- /** The list of identity types that require activation through an offlineLicenseToken */
163
- export const IdentityTypesRequiringOfflineLicenseToken = ['manual', 'sharedKey', 'offlinePlayground']
@@ -1,71 +0,0 @@
1
- //
2
- // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
- //
4
-
5
- import * as FFI from './ffi'
6
- import * as Environment from './@environment'
7
-
8
- import { fullBuildVersionString, defaultDittoWasmFileURL } from './build-time-constants'
9
- import type { WebAssemblyModule } from './ffi'
10
-
11
- let isInitialized = false
12
-
13
- export { WebAssemblyModule }
14
-
15
- /**
16
- * Available options for {@link init | init()}.
17
- */
18
- export type InitOptions = {
19
- /**
20
- * You can explicitly pass the WebAssembly module or its location via the
21
- * `webAssemblyModule` option. By default, Ditto tries to load the WebAssembly
22
- * module from the same path where this JavaScript is served.
23
- */
24
- webAssemblyModule?: WebAssemblyModule
25
- }
26
-
27
- /**
28
- * Initializes the whole Ditto module. Needs to be called and complete before
29
- * any of the Ditto API is used.
30
- *
31
- * @param options - Dictionary with global {@link InitOptions | initialization options}.
32
- */
33
- export async function init(options: InitOptions = {}): Promise<void> {
34
- if (!isInitialized) {
35
- if (Environment.isWebBuild) {
36
- // IDEA: throw or log a warning if init() was called a second time with
37
- // different options (because those wouldn't have any effect).
38
-
39
- const webAssemblyModule = options.webAssemblyModule || defaultDittoWasmFileURL
40
-
41
- await FFI.init(webAssemblyModule)
42
-
43
- FFI.initSDKVersion('Web', 'JavaScript', fullBuildVersionString)
44
- FFI.loggerInit()
45
- }
46
-
47
- isInitialized = true
48
- }
49
- }
50
-
51
- if (Environment.isNodeBuild) {
52
- switch (process.platform) {
53
- case 'android':
54
- FFI.initSDKVersion('Android', 'JavaScript', fullBuildVersionString)
55
- break
56
- case 'darwin':
57
- FFI.initSDKVersion('Mac', 'JavaScript', fullBuildVersionString)
58
- break
59
- case 'linux':
60
- FFI.initSDKVersion('Linux', 'JavaScript', fullBuildVersionString)
61
- break
62
- case 'win32':
63
- FFI.initSDKVersion('Windows', 'JavaScript', fullBuildVersionString)
64
- break
65
- default:
66
- FFI.initSDKVersion('Unknown', 'JavaScript', fullBuildVersionString)
67
- break
68
- }
69
-
70
- FFI.loggerInit()
71
- }
@@ -1,143 +0,0 @@
1
- //
2
- // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
- //
4
-
5
- import * as FFI from './ffi'
6
- import * as Environment from './@environment'
7
-
8
- // ------------------------------------------------------------ Constants ------
9
-
10
- export const defaultDittoCloudDomain = `cloud.ditto.live`
11
-
12
- // ------------------------------------------------------------- Defaults ------
13
-
14
- /** @internal */
15
- export function defaultAuthURL(appID: string): string {
16
- return `https://${appID}.${defaultDittoCloudDomain}`
17
- }
18
-
19
- /** @internal */
20
- export function defaultDittoCloudURL(appID: string): string {
21
- return `wss://${appID}.${defaultDittoCloudDomain}`
22
- }
23
-
24
- // ---------------------------------------------------------- Validations ------
25
-
26
- /**
27
- * Validates a number and returns it as-is if all requirements are met,
28
- * otherwise throws an exception. Options:
29
- *
30
- * - `integer`: if `true`, requires the number to be an integer
31
- *
32
- * - `min`: if given, requires the number to be >= `min`
33
- * - `max`: if given, requires the number to be <= `max`
34
- *
35
- * - `minX`: if given, requires the number to be > `minX` (x stands for "exclusive")
36
- * - `maxX`: if given, requires the number to be < `maxX` (x stands for "exclusive")
37
- *
38
- * You can also customize the error message by providing a prefix via
39
- * `errorMessagePrefix`, which defaults to `"Number validation failed:"`.
40
- *
41
- * @internal
42
- */
43
- export function validateNumber(value, options = {}): number {
44
- const errorMessagePrefix = options['errorMessagePrefix'] ?? 'Number validation failed:'
45
- const integer = !!options['integer']
46
-
47
- const min = options['min']
48
- const max = options['max']
49
-
50
- const minX = options['minX']
51
- const maxX = options['maxX']
52
-
53
- if (typeof value !== 'number') throw new Error(`${errorMessagePrefix} '${value}' is not a number.`)
54
- if (integer && Math.floor(value) !== value) throw new Error(`${errorMessagePrefix} '${value}' is not an integer.`)
55
-
56
- if (typeof min !== 'undefined' && value < min) throw new Error(`${errorMessagePrefix} '${value}' must be >= ${min}.`)
57
- if (typeof max !== 'undefined' && value > max) throw new Error(`${errorMessagePrefix} '${value}' must be <= ${max}.`)
58
-
59
- if (typeof minX !== 'undefined' && value <= minX) throw new Error(`${errorMessagePrefix} '${value}' must be > ${minX}.`)
60
- if (typeof maxX !== 'undefined' && value >= maxX) throw new Error(`${errorMessagePrefix} '${value}' must be < ${maxX}.`)
61
-
62
- return value
63
- }
64
-
65
- export function validateQuery(query, options = {}): string {
66
- const errorMessagePrefix = options['errorMessagePrefix'] ?? 'Query validation failed,'
67
-
68
- if (typeof query !== 'string') throw new Error(`${errorMessagePrefix} query is not a string: ${query}`)
69
- if (query === '') throw new Error(`${errorMessagePrefix} query is an empty string.`)
70
-
71
- const validatedQuery = query.trim()
72
- if (validatedQuery === '') throw new Error(`${errorMessagePrefix} query contains only whitespace characters.`)
73
- return validatedQuery
74
- }
75
-
76
- // -------------------------------------------------------------- Helpers ------
77
-
78
- /**
79
- * Generate a random hex-encoded token using the WebCrypto API.
80
- *
81
- * @internal */
82
- export function generateEphemeralToken(): string {
83
-
84
- let data = new Uint16Array(16)
85
- if (Environment.isReactNativeBuild) {
86
- data = FFI.getRandomValues(data) as Uint16Array
87
- } else {
88
- throw new Error('Internal inconsistency, incorrect environment to run getRandomValues()')
89
- }
90
-
91
- const doublets = Array.from(data)
92
- return doublets.map((doublet) => doublet.toString(16)).join('')
93
- }
94
-
95
- /**
96
- * Can be used to implement a custom Node.js `inspect` representation for
97
- * objects that have a `value` property.
98
- *
99
- * Node.js < 16.14.0 and Electron < 19.0.0 do not provide the `inspect`
100
- * parameter used below, which is when we fall back to `JSON.stringify`.
101
- *
102
- * See https://nodejs.org/api/util.html#custom-inspection-functions-on-objects
103
- */
104
- export function customInspectRepresentation(documentLike: { value: any }, inspect?: any): string {
105
- if (inspect === undefined) {
106
- return `${documentLike.constructor.name} ${JSON.stringify({ value: documentLike.value }, null, 2)}`
107
- }
108
- return `${documentLike.constructor.name} ${inspect({ value: documentLike.value })}`
109
- }
110
-
111
- // --------------------------------------------------------------- System ------
112
-
113
- /** @internal */
114
- export function sleep(milliseconds: number): Promise<void> {
115
- return new Promise((resolve, reject) => {
116
- setTimeout(resolve, milliseconds)
117
- })
118
- }
119
-
120
- /** @internal use this to asyncify chunks of code. */
121
- export async function step<T>(closure: () => T): Promise<T> {
122
- await sleep(0)
123
- return closure()
124
- }
125
-
126
- /** @internal */
127
- // WORKAROUND: the corresponding FFI function(s) is not async at the
128
- // moment, we therefore artificially make it async via step() until an
129
- // async variant becomes available.
130
- //
131
- // Why? Any function marked as async that isn't under the hood appears to be
132
- // yield-ing on the use site but isn't. This may lead to blocking the
133
- // event loop for a long time, which in turn can lead to excessive memory
134
- // consumption and other side-effects. Plus, WebAssembly limitations may
135
- // lead to deadlocks and other crashes.
136
- //
137
- // The function alias here is to be able to easily see that its use is
138
- // a workaround, plus easily finding all of those workarounds to fix
139
- // them all once the FFI API is properly asyncified.
140
- //
141
- // See PR #4833 for details:
142
- // https://github.com/getditto/ditto/pull/4833
143
- export const performAsyncToWorkaroundNonAsyncFFIAPI = step
@@ -1,73 +0,0 @@
1
- //
2
- // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
- //
4
-
5
- /** @internal */
6
- export class KeepAlive {
7
- /** @internal */
8
- get isActive(): boolean {
9
- return this.intervalID !== null
10
- }
11
-
12
- /** @internal */
13
- constructor() {
14
- this.countsByID = {}
15
- this.intervalID = null
16
- }
17
-
18
- /** @internal */
19
- retain(id: string): void {
20
- if (typeof this.countsByID[id] === 'undefined') {
21
- this.countsByID[id] = 0
22
- }
23
-
24
- this.countsByID[id] += 1
25
-
26
- if (this.intervalID === null) {
27
- // Keep the process alive as long as there is at least one ID being
28
- // tracked by setting a time interval with the maximum delay. This will
29
- // prevent the process from exiting.
30
- const maxDelay = 2147483647 // Signed 32 bit integer, see docs: https://developer.mozilla.org/en-US/docs/Web/API/setInterval
31
- this.intervalID = setInterval(() => {
32
- /* no-op */
33
- }, maxDelay)
34
- KeepAlive.finalizationRegistry.register(this, this.intervalID, this)
35
- }
36
- }
37
-
38
- /** @internal */
39
- release(id: string): void {
40
- if (typeof this.countsByID[id] === 'undefined') {
41
- throw new Error(`Internal inconsistency, trying to release a keep-alive ID that hasn't been retained before or isn't tracked anymore: ${id}`)
42
- }
43
-
44
- this.countsByID[id] -= 1
45
-
46
- if (this.countsByID[id] === 0) {
47
- delete this.countsByID[id]
48
- }
49
-
50
- if (Object.keys(this.countsByID).length === 0) {
51
- // Nothing is tracked anymore, it's safe to clear the interval
52
- // and let the process do what it wants.
53
- KeepAlive.finalizationRegistry.unregister(this)
54
- clearInterval(this.intervalID)
55
- this.intervalID = null
56
- }
57
- }
58
-
59
- /** @internal */
60
- currentIDs(): string[] {
61
- return Object.keys(this.countsByID)
62
- }
63
-
64
- /** @internal */
65
- countForID(id: string): number | null {
66
- return this.countsByID[id] ?? null
67
- }
68
-
69
- private static finalizationRegistry = new FinalizationRegistry(clearInterval)
70
-
71
- private intervalID
72
- private countsByID
73
- }
@@ -1,198 +0,0 @@
1
- //
2
- // Copyright © 2021 DittoLive Incorporated. All rights reserved.
3
- //
4
-
5
- const regularKeyPattern = /\.([A-Za-z_]\w*)/.source
6
- const subscriptIndexPattern = /\[(\d+)\]/.source
7
- const subscriptSingleQuoteKeyPattern = /\[\'(.+?)\'\]/.source
8
- const subscriptDoubleQuoteKeyPattern = /\[\"(.+?)\"\]/.source
9
- const validKeyPathPattern = `((${regularKeyPattern})|(${subscriptIndexPattern})|(${subscriptSingleQuoteKeyPattern})|(${subscriptDoubleQuoteKeyPattern}))*`
10
-
11
- const regularKeyRegExp = new RegExp(`^${regularKeyPattern}`)
12
- const subscriptIndexRegExp = new RegExp(`^${subscriptIndexPattern}`)
13
- const subscriptSingleQuoteKeyRegExp = new RegExp(`^${subscriptSingleQuoteKeyPattern}`)
14
- const subscriptDoubleQuoteKeyRegExp = new RegExp(`^${subscriptDoubleQuoteKeyPattern}`)
15
- const validKeyPathRegExp = new RegExp(`^${validKeyPathPattern}$`)
16
-
17
- export type KeyPathValidateOptions = {
18
- isInitial?: boolean // default: false
19
- }
20
-
21
- export type KeyPathEvaluateOptions = {
22
- stopAtLastContainer?: boolean // default: false
23
- }
24
-
25
- export type KeyPathEvaluationResult = {
26
- keyPath: string | number
27
- object: any
28
- options: KeyPathEvaluateOptions
29
- coveredPath: string | number
30
- nextPathComponent: string | number | null
31
- remainingPath: string | number
32
- value: any
33
- }
34
-
35
- /**
36
- * Namespace for key-path related functions.
37
- * @internal
38
- */
39
- export class KeyPath {
40
- /**
41
- * Returns the key-path by prefixing it with a leading dot if first key
42
- * starts with a character or an underscore.
43
- */
44
- static withLeadingDot(keyPath: string | number): any /* string | number */ {
45
- if (typeof keyPath === 'number') {
46
- return keyPath
47
- }
48
-
49
- if (typeof keyPath !== 'string') {
50
- throw new Error(`Key-path must be a string or a number but is ${typeof keyPath}: ${keyPath}`)
51
- }
52
-
53
- const needsLeadingDot = typeof keyPath === 'string' && !!keyPath.match(/^[A-Za-z_]/)
54
- return needsLeadingDot ? `.${keyPath}` : keyPath
55
- }
56
-
57
- /**
58
- * Returns the key-path by removing the leading dot if it starts with one.
59
- */
60
- static withoutLeadingDot(keyPath: string | number): any /* string | number */ {
61
- if (typeof keyPath === 'number') {
62
- return keyPath
63
- }
64
-
65
- if (typeof keyPath !== 'string') {
66
- throw new Error(`Key-path must be a string or a number but is ${typeof keyPath}: ${keyPath}`)
67
- }
68
-
69
- const hasLeadingDot = typeof keyPath === 'string' && !!keyPath.match(/^\./)
70
- return hasLeadingDot ? keyPath.slice(1) : keyPath
71
- }
72
-
73
- /**
74
- * Validates a key-path by adding a leading dot if appropriate and checking
75
- * its syntax. Throws if syntax is invalid.
76
- */
77
- static validate(keyPath: string | number, options: KeyPathValidateOptions = {}): any /* string | number */ {
78
- const isInitial = options.isInitial ?? false
79
-
80
- if (typeof keyPath === 'number') {
81
- return Math.floor(Math.abs(keyPath))
82
- }
83
-
84
- if (typeof keyPath !== 'string') {
85
- throw new Error(`Key-path must be a string or a number but is ${typeof keyPath}: ${keyPath}`)
86
- }
87
-
88
- const keyPathWithLeadingDot = this.withLeadingDot(keyPath) as string
89
-
90
- if (!validKeyPathRegExp.test(keyPathWithLeadingDot)) {
91
- throw new Error(`Key-path is not valid: ${keyPath}`)
92
- }
93
-
94
- return isInitial ? keyPath : keyPathWithLeadingDot
95
- }
96
-
97
- /**
98
- * Follows the `keyPath`, boring down through `object` recursively until the
99
- * final key-path is reached. Implemented as a free function to help ensure it's
100
- * purely functional in nature and that no modifications are made to a
101
- * `Document{ID}` or `DocumentPath` where similarly named variables are at play.
102
- * @internal
103
- */
104
- static evaluate(keyPath: string | number, object: any, options: KeyPathEvaluateOptions = {}): KeyPathEvaluationResult {
105
- const stopAtLastContainer = options.stopAtLastContainer ?? false
106
-
107
- const evaluationResult = {
108
- keyPath: keyPath,
109
- object: object,
110
- options: { ...options },
111
- coveredPath: null,
112
- nextPathComponent: null,
113
- remainingPath: keyPath,
114
- value: object,
115
- }
116
-
117
- function advance(keyPath: string | number): { nextPathComponentRaw: string | number; nextPathComponent: string | number; remainingPath: string } {
118
- if (typeof keyPath === 'number') {
119
- return { nextPathComponentRaw: keyPath, nextPathComponent: keyPath, remainingPath: '' }
120
- }
121
-
122
- if (typeof keyPath !== 'string') {
123
- throw new Error(`Can't return value at given keyPath, expected keyPath to be a string or a number but got ${typeof keyPath}: ${keyPath}`)
124
- }
125
-
126
- // NOTE: `nextPathComponentRaw` represents next path component plus all
127
- // of its surrounding delimiters, i.e. given a path `["abc"].blah.blub`,
128
- // `nextPathComponentRaw` would be `["abc"]` while nextPathComponent
129
- // would just yield 'abc'.
130
-
131
- const regularKeyMatch = keyPath.match(regularKeyRegExp)
132
- if (regularKeyMatch !== null) {
133
- const nextPathComponentRaw = regularKeyMatch[0]
134
- const nextPathComponent = regularKeyMatch[1]
135
- const remainingPath = keyPath.slice(nextPathComponent.length + /* stripped out . */ 1)
136
- return { nextPathComponentRaw, nextPathComponent, remainingPath }
137
- }
138
-
139
- const subscriptIndexMatch = keyPath.match(subscriptIndexRegExp)
140
- if (subscriptIndexMatch !== null) {
141
- const nextPathComponentRaw = subscriptIndexMatch[0]
142
- const nextPathComponentString = subscriptIndexMatch[1]
143
- const nextPathComponent = parseInt(nextPathComponentString)
144
- const remainingPath = keyPath.slice(nextPathComponentString.length + /* stripped out [] */ 2)
145
- return { nextPathComponentRaw, nextPathComponent, remainingPath }
146
- }
147
-
148
- const subscriptSingleQuoteMatch = keyPath.match(subscriptSingleQuoteKeyRegExp)
149
- if (subscriptSingleQuoteMatch !== null) {
150
- const nextPathComponentRaw = subscriptSingleQuoteMatch[0]
151
- const nextPathComponent = subscriptSingleQuoteMatch[1]
152
- const remainingPath = keyPath.slice(nextPathComponent.length + /* stripped out [''] */ 4)
153
- return { nextPathComponentRaw, nextPathComponent, remainingPath }
154
- }
155
-
156
- const subscriptDoubleQuoteMatch = keyPath.match(subscriptDoubleQuoteKeyRegExp)
157
- if (subscriptDoubleQuoteMatch !== null) {
158
- const nextPathComponentRaw = subscriptDoubleQuoteMatch[0]
159
- const nextPathComponent = subscriptDoubleQuoteMatch[1]
160
- const remainingPath = keyPath.slice(nextPathComponent.length + /* stripped out [""] */ 4)
161
- return { nextPathComponentRaw, nextPathComponent, remainingPath }
162
- }
163
-
164
- throw new Error(`Can't return value at keyPath because the following part of the keyPath is invalid: ${keyPath}`)
165
- }
166
-
167
- function recurse(object: any, keyPath: string | number): KeyPathEvaluationResult {
168
- if (keyPath === '') {
169
- evaluationResult.value = object
170
- return evaluationResult
171
- }
172
-
173
- const { nextPathComponentRaw, nextPathComponent, remainingPath } = advance(keyPath)
174
-
175
- evaluationResult.nextPathComponent = nextPathComponent
176
- evaluationResult.remainingPath = remainingPath
177
-
178
- if (evaluationResult.coveredPath === null || typeof nextPathComponentRaw === 'number') {
179
- evaluationResult.coveredPath = nextPathComponentRaw
180
- } else {
181
- evaluationResult.coveredPath += nextPathComponentRaw
182
- }
183
-
184
- if (remainingPath === '' && stopAtLastContainer) {
185
- evaluationResult.value = object
186
- return evaluationResult
187
- }
188
-
189
- const nextObject = object[nextPathComponent]
190
- return recurse(nextObject, remainingPath)
191
- }
192
-
193
- const keyPathWithLeadingDot = this.withLeadingDot(keyPath)
194
- return recurse(object, keyPathWithLeadingDot)
195
- }
196
-
197
- private constructor() {}
198
- }