@evolu/react-native 15.0.0-next.0 → 15.0.0-next.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/LICENSE +21 -0
- package/dist/src/LockManager.d.ts +13 -0
- package/dist/src/LockManager.d.ts.map +1 -0
- package/dist/src/LockManager.js +219 -0
- package/dist/src/Task.d.ts +4 -4
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +2 -2
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/shared.d.ts.map +1 -1
- package/dist/src/shared.js +8 -5
- package/dist/src/sqlite-drivers/createExpoSqliteDriver.d.ts.map +1 -1
- package/dist/src/sqlite-drivers/createExpoSqliteDriver.js +128 -45
- package/dist/src/sqlite-drivers/createOpSqliteDriver.d.ts.map +1 -1
- package/dist/src/sqlite-drivers/createOpSqliteDriver.js +115 -33
- package/package.json +16 -14
- package/src/LockManager.ts +325 -0
- package/src/Polyfills.ts +5 -9
- package/src/Task.ts +5 -5
- package/src/index.ts +1 -1
- package/src/shared.ts +17 -12
- package/src/sqlite-drivers/createExpoSqliteDriver.ts +23 -8
- package/src/sqlite-drivers/createOpSqliteDriver.ts +16 -4
- package/dist/src/Worker.d.ts +0 -30
- package/dist/src/Worker.d.ts.map +0 -1
- package/dist/src/Worker.js +0 -29
- package/src/Worker.ts +0 -54
|
@@ -1,37 +1,119 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
1
53
|
import { bytesToHex, createPreparedStatementsCache, lazyVoid, ok, } from "@evolu/common";
|
|
2
54
|
import { open } from "@op-engineering/op-sqlite";
|
|
3
55
|
export const createOpSqliteDriver = (name, options) => () => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
name: `
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
57
|
+
try {
|
|
58
|
+
// https://op-engineering.github.io/op-sqlite/docs/configuration#in-memory
|
|
59
|
+
const disposer = __addDisposableResource(env_1, new DisposableStack(), false);
|
|
60
|
+
const db = disposer.adopt(open(options?.mode === "memory"
|
|
61
|
+
? { name: `inMemoryDb`, location: ":memory:" }
|
|
62
|
+
: {
|
|
63
|
+
name: `evolu1-${name}.db`,
|
|
64
|
+
...(options?.mode === "encrypted" && {
|
|
65
|
+
encryptionKey: `x'${bytesToHex(options.encryptionKey)}'`,
|
|
66
|
+
}),
|
|
67
|
+
}), (db) => {
|
|
68
|
+
db.close();
|
|
69
|
+
});
|
|
70
|
+
const cache = disposer.use(createPreparedStatementsCache((sql) => db.prepareStatement(sql),
|
|
71
|
+
// op-sqlite doesn't have API for that
|
|
72
|
+
lazyVoid));
|
|
73
|
+
const disposables = disposer.move();
|
|
74
|
+
return ok({
|
|
75
|
+
exec: (query) => {
|
|
76
|
+
const prepared = cache.get(query);
|
|
77
|
+
if (prepared) {
|
|
78
|
+
prepared.bindSync(query.parameters);
|
|
79
|
+
}
|
|
80
|
+
const { rows, rowsAffected } = db.executeSync(query.sql, query.parameters);
|
|
81
|
+
return { rows: rows, changes: rowsAffected };
|
|
82
|
+
},
|
|
83
|
+
// FIXME: op-sqlite does not expose binary, but a path to the database file
|
|
84
|
+
// another react native dependency would be needed to implement this
|
|
85
|
+
export: () => {
|
|
86
|
+
throw new Error("TODO: Not implemented yet");
|
|
87
|
+
},
|
|
88
|
+
deleteDatabase: () => {
|
|
89
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
90
|
+
try {
|
|
91
|
+
const deleteDisposer = __addDisposableResource(env_2, new DisposableStack(), false);
|
|
92
|
+
if (options?.mode !== "memory") {
|
|
93
|
+
deleteDisposer.defer(() => {
|
|
94
|
+
db.delete();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
deleteDisposer.use(disposables);
|
|
98
|
+
}
|
|
99
|
+
catch (e_2) {
|
|
100
|
+
env_2.error = e_2;
|
|
101
|
+
env_2.hasError = true;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
__disposeResources(env_2);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
[Symbol.dispose]: () => {
|
|
108
|
+
disposables.dispose();
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (e_1) {
|
|
113
|
+
env_1.error = e_1;
|
|
114
|
+
env_1.hasError = true;
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
__disposeResources(env_1);
|
|
118
|
+
}
|
|
37
119
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/react-native",
|
|
3
|
-
"version": "15.0.0-next.
|
|
3
|
+
"version": "15.0.0-next.2",
|
|
4
4
|
"description": "Evolu for React Native and Expo",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"evolu",
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
],
|
|
13
13
|
"author": "Daniel Steigerwald <daniel@steigerwald.cz>",
|
|
14
14
|
"license": "MIT",
|
|
15
|
-
"repository":
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/evoluhq/evolu.git"
|
|
18
|
+
},
|
|
16
19
|
"bugs": {
|
|
17
20
|
"url": "https://github.com/evoluhq/evolu/issues"
|
|
18
21
|
},
|
|
@@ -72,11 +75,6 @@
|
|
|
72
75
|
"src/**",
|
|
73
76
|
"README.md"
|
|
74
77
|
],
|
|
75
|
-
"scripts": {
|
|
76
|
-
"build": "tsc --build tsconfig.build.json",
|
|
77
|
-
"prepack": "npm run build",
|
|
78
|
-
"format": "prettier --write \"src/*.{ts,tsx,md}\""
|
|
79
|
-
},
|
|
80
78
|
"dependencies": {
|
|
81
79
|
"array-from-async": "^3.0.0",
|
|
82
80
|
"array.prototype.tosorted": "^1.1.4",
|
|
@@ -91,20 +89,20 @@
|
|
|
91
89
|
"set.prototype.union": "^1.1.3"
|
|
92
90
|
},
|
|
93
91
|
"devDependencies": {
|
|
94
|
-
"@evolu/common": "8.0.0-next.0",
|
|
95
|
-
"@evolu/react": "11.0.0-next.0",
|
|
96
|
-
"@evolu/typescript-config": "*",
|
|
97
92
|
"@op-engineering/op-sqlite": "^15.2.2",
|
|
98
93
|
"@types/react": "^19.2.14",
|
|
99
94
|
"expo": "^55.0.4",
|
|
100
95
|
"expo-secure-store": "~55.0.8",
|
|
101
96
|
"expo-sqlite": "~55.0.10",
|
|
102
97
|
"react": "^19.2.4",
|
|
103
|
-
"react-native": "0.83.
|
|
98
|
+
"react-native": "0.83.6",
|
|
104
99
|
"react-native-nitro-modules": "0.34.1",
|
|
105
100
|
"react-native-sensitive-info": "6.0.0-rc.11",
|
|
106
101
|
"react-native-svg": "15.15.3",
|
|
107
|
-
"typescript": "^
|
|
102
|
+
"typescript": "^6",
|
|
103
|
+
"@evolu/common": "8.0.0-next.5",
|
|
104
|
+
"@evolu/react": "11.0.0-next.0",
|
|
105
|
+
"@evolu/typescript-config": "0.0.2"
|
|
108
106
|
},
|
|
109
107
|
"peerDependencies": {
|
|
110
108
|
"@evolu/common": "^8.0.0-next.0",
|
|
@@ -148,5 +146,9 @@
|
|
|
148
146
|
"engines": {
|
|
149
147
|
"node": ">=24.0.0"
|
|
150
148
|
},
|
|
151
|
-
"sideEffects": false
|
|
152
|
-
|
|
149
|
+
"sideEffects": false,
|
|
150
|
+
"scripts": {
|
|
151
|
+
"build": "tsc --build tsconfig.build.json",
|
|
152
|
+
"format": "prettier --write \"src/*.{ts,tsx,md}\""
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native implementation of {@link LockManager}.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* React Native `LockManager` ponyfill.
|
|
9
|
+
*
|
|
10
|
+
* This is a temporary in-memory ponyfill until React Native ships native
|
|
11
|
+
* `LockManager` support.
|
|
12
|
+
*
|
|
13
|
+
* The ponyfill follows Web Locks request and query semantics within the current
|
|
14
|
+
* JavaScript process.
|
|
15
|
+
*
|
|
16
|
+
* ### Example
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* await lockManager.request("db", async () => {
|
|
20
|
+
* // critical section
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
const createLockManagerPonyfill = ({
|
|
25
|
+
clientId = "in-memory-main-thread",
|
|
26
|
+
}: {
|
|
27
|
+
readonly clientId?: string;
|
|
28
|
+
} = {}): LockManager => {
|
|
29
|
+
const heldLocksByName = new Map<string, Array<HeldLock>>();
|
|
30
|
+
const pendingRequestsByName = new Map<string, Array<PendingRequest<any>>>();
|
|
31
|
+
|
|
32
|
+
const processPendingRequests = (name: string): void => {
|
|
33
|
+
const pendingRequests = pendingRequestsByName.get(name);
|
|
34
|
+
if (pendingRequests == null || pendingRequests.length === 0) return;
|
|
35
|
+
|
|
36
|
+
const heldLocks = heldLocksByName.get(name) ?? [];
|
|
37
|
+
const hasExclusiveLock = heldLocks.some(
|
|
38
|
+
(lock) => lock.mode === "exclusive",
|
|
39
|
+
);
|
|
40
|
+
if (hasExclusiveLock) return;
|
|
41
|
+
|
|
42
|
+
if (heldLocks.length === 0) {
|
|
43
|
+
const nextRequest = pendingRequests[0];
|
|
44
|
+
if (nextRequest.mode === "exclusive") {
|
|
45
|
+
grantPendingRequest(name, pendingRequests.shift()!);
|
|
46
|
+
} else {
|
|
47
|
+
while (pendingRequests[0]?.mode === "shared") {
|
|
48
|
+
grantPendingRequest(name, pendingRequests.shift()!);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
while (pendingRequests[0]?.mode === "shared") {
|
|
53
|
+
grantPendingRequest(name, pendingRequests.shift()!);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
cleanupNameState(name);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const grantPendingRequest = <T>(
|
|
61
|
+
name: string,
|
|
62
|
+
pendingRequest: PendingRequest<T>,
|
|
63
|
+
): void => {
|
|
64
|
+
const heldLocks = heldLocksByName.get(name) ?? [];
|
|
65
|
+
const heldLock = {
|
|
66
|
+
clientId,
|
|
67
|
+
mode: pendingRequest.mode,
|
|
68
|
+
name,
|
|
69
|
+
reject: pendingRequest.reject,
|
|
70
|
+
} satisfies HeldLock;
|
|
71
|
+
|
|
72
|
+
heldLocks.push(heldLock);
|
|
73
|
+
heldLocksByName.set(name, heldLocks);
|
|
74
|
+
pendingRequest.phase = "granted";
|
|
75
|
+
|
|
76
|
+
void Promise.resolve()
|
|
77
|
+
.then(() => {
|
|
78
|
+
if (pendingRequest.signal?.aborted) {
|
|
79
|
+
pendingRequest.phase = "settled";
|
|
80
|
+
pendingRequest.signal.removeEventListener(
|
|
81
|
+
"abort",
|
|
82
|
+
pendingRequest.abort,
|
|
83
|
+
);
|
|
84
|
+
releaseHeldLock(name, heldLock);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pendingRequest.signal?.removeEventListener(
|
|
89
|
+
"abort",
|
|
90
|
+
pendingRequest.abort,
|
|
91
|
+
);
|
|
92
|
+
pendingRequest.phase = "running";
|
|
93
|
+
|
|
94
|
+
return Promise.resolve(
|
|
95
|
+
pendingRequest.callback({ mode: pendingRequest.mode, name }),
|
|
96
|
+
);
|
|
97
|
+
})
|
|
98
|
+
.then(
|
|
99
|
+
(value) => {
|
|
100
|
+
if (pendingRequest.phase === "settled") return;
|
|
101
|
+
|
|
102
|
+
pendingRequest.phase = "settled";
|
|
103
|
+
releaseHeldLock(name, heldLock);
|
|
104
|
+
pendingRequest.resolve(value!);
|
|
105
|
+
},
|
|
106
|
+
(error: unknown) => {
|
|
107
|
+
pendingRequest.phase = "settled";
|
|
108
|
+
releaseHeldLock(name, heldLock);
|
|
109
|
+
pendingRequest.reject(error);
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const releaseHeldLock = (name: string, heldLock: HeldLock): void => {
|
|
115
|
+
const currentHeldLocks = heldLocksByName.get(name) ?? [];
|
|
116
|
+
heldLocksByName.set(
|
|
117
|
+
name,
|
|
118
|
+
currentHeldLocks.filter((lock) => lock !== heldLock),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
cleanupNameState(name);
|
|
122
|
+
processPendingRequests(name);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const stealLocks = (name: string): void => {
|
|
126
|
+
const heldLocks = heldLocksByName.get(name) ?? [];
|
|
127
|
+
if (heldLocks.length === 0) return;
|
|
128
|
+
|
|
129
|
+
heldLocksByName.delete(name);
|
|
130
|
+
|
|
131
|
+
for (const heldLock of heldLocks) {
|
|
132
|
+
heldLock.reject(createAbortError());
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const cleanupNameState = (name: string): void => {
|
|
137
|
+
if (heldLocksByName.get(name)?.length === 0) {
|
|
138
|
+
heldLocksByName.delete(name);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (pendingRequestsByName.get(name)?.length === 0) {
|
|
142
|
+
pendingRequestsByName.delete(name);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const removePendingRequest = <T>(
|
|
147
|
+
name: string,
|
|
148
|
+
pendingRequest: PendingRequest<T>,
|
|
149
|
+
): void => {
|
|
150
|
+
const pendingRequests = pendingRequestsByName.get(name)!;
|
|
151
|
+
pendingRequestsByName.set(
|
|
152
|
+
name,
|
|
153
|
+
pendingRequests.filter((request) => request !== pendingRequest),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
cleanupNameState(name);
|
|
157
|
+
processPendingRequests(name);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const canGrantImmediately = (name: string, mode: LockMode): boolean => {
|
|
161
|
+
const pendingRequests = pendingRequestsByName.get(name);
|
|
162
|
+
if (pendingRequests != null && pendingRequests.length > 0) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const heldLocks = heldLocksByName.get(name) ?? [];
|
|
167
|
+
|
|
168
|
+
if (mode === "exclusive") {
|
|
169
|
+
return heldLocks.length === 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return heldLocks.every((lock) => lock.mode === "shared");
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
request: <T>(
|
|
177
|
+
name: string,
|
|
178
|
+
optionsOrCallback: RequestOptions | LockGrantedCallback<T>,
|
|
179
|
+
maybeCallback?: LockGrantedCallback<T>,
|
|
180
|
+
): Promise<Awaited<T>> => {
|
|
181
|
+
const [options, callback] = normalizeRequestArguments(
|
|
182
|
+
optionsOrCallback,
|
|
183
|
+
maybeCallback,
|
|
184
|
+
);
|
|
185
|
+
const mode = options.mode ?? "exclusive";
|
|
186
|
+
|
|
187
|
+
if (name.startsWith("-")) {
|
|
188
|
+
return Promise.reject(createNotSupportedError());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (options.ifAvailable && options.steal) {
|
|
192
|
+
return Promise.reject(createNotSupportedError());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (options.steal && mode !== "exclusive") {
|
|
196
|
+
return Promise.reject(createNotSupportedError());
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (options.signal != null && (options.ifAvailable || options.steal)) {
|
|
200
|
+
return Promise.reject(createNotSupportedError());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (options.signal?.aborted) {
|
|
204
|
+
return Promise.reject(options.signal.reason as Error);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (options.ifAvailable && !canGrantImmediately(name, mode)) {
|
|
208
|
+
return Promise.resolve().then(() => Promise.resolve(callback(null)));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return new Promise<Awaited<T>>((resolve, reject) => {
|
|
212
|
+
const pendingRequest: PendingRequest<T> = {
|
|
213
|
+
abort: () => {
|
|
214
|
+
if (pendingRequest.phase === "queued") {
|
|
215
|
+
pendingRequest.phase = "settled";
|
|
216
|
+
removePendingRequest(name, pendingRequest);
|
|
217
|
+
reject(options.signal!.reason as Error);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
pendingRequest.phase = "settled";
|
|
222
|
+
reject(options.signal!.reason as Error);
|
|
223
|
+
},
|
|
224
|
+
callback,
|
|
225
|
+
mode,
|
|
226
|
+
name,
|
|
227
|
+
phase: "queued",
|
|
228
|
+
reject,
|
|
229
|
+
resolve,
|
|
230
|
+
...(options.signal == null ? {} : { signal: options.signal }),
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
if (options.signal != null) {
|
|
234
|
+
options.signal.addEventListener("abort", pendingRequest.abort, {
|
|
235
|
+
once: true,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const pendingRequests = pendingRequestsByName.get(name) ?? [];
|
|
240
|
+
|
|
241
|
+
if (options.steal) {
|
|
242
|
+
stealLocks(name);
|
|
243
|
+
pendingRequests.unshift(pendingRequest);
|
|
244
|
+
} else {
|
|
245
|
+
pendingRequests.push(pendingRequest);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
pendingRequestsByName.set(name, pendingRequests);
|
|
249
|
+
processPendingRequests(name);
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
query: () => {
|
|
254
|
+
const held = Array.from(heldLocksByName.values())
|
|
255
|
+
.flat()
|
|
256
|
+
.map(({ clientId, mode, name }) => ({ clientId, mode, name }));
|
|
257
|
+
const pending = Array.from(pendingRequestsByName.values())
|
|
258
|
+
.flat()
|
|
259
|
+
.map(({ mode, name }) => ({
|
|
260
|
+
clientId,
|
|
261
|
+
mode,
|
|
262
|
+
name,
|
|
263
|
+
}));
|
|
264
|
+
|
|
265
|
+
return Promise.resolve({ held, pending });
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* React Native {@link LockManager}.
|
|
272
|
+
*
|
|
273
|
+
* Uses an in-memory lock manager scoped to the main JavaScript thread while
|
|
274
|
+
* following Web Locks request and query semantics.
|
|
275
|
+
*/
|
|
276
|
+
export const lockManager: LockManager = /*#__PURE__*/ createLockManagerPonyfill(
|
|
277
|
+
{
|
|
278
|
+
clientId: "react-native-main-thread",
|
|
279
|
+
},
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const normalizeRequestArguments = <T>(
|
|
283
|
+
optionsOrCallback: RequestOptions | LockGrantedCallback<T>,
|
|
284
|
+
maybeCallback?: LockGrantedCallback<T>,
|
|
285
|
+
): [RequestOptions, LockGrantedCallback<T>] =>
|
|
286
|
+
typeof optionsOrCallback === "function"
|
|
287
|
+
? [{}, optionsOrCallback]
|
|
288
|
+
: [optionsOrCallback, maybeCallback!];
|
|
289
|
+
|
|
290
|
+
const createAbortError = (): Error =>
|
|
291
|
+
createNamedError("AbortError", "The request was aborted.");
|
|
292
|
+
|
|
293
|
+
const createNotSupportedError = (): Error =>
|
|
294
|
+
createNamedError("NotSupportedError", "The operation is not supported.");
|
|
295
|
+
|
|
296
|
+
const createNamedError = (name: string, message: string): Error => {
|
|
297
|
+
const error = new Error(message) as Error & { name: string };
|
|
298
|
+
error.name = name;
|
|
299
|
+
return error;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
interface HeldLock extends LockInfo {
|
|
303
|
+
readonly clientId: string;
|
|
304
|
+
readonly mode: LockMode;
|
|
305
|
+
readonly name: string;
|
|
306
|
+
readonly reject: (reason?: unknown) => void;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
interface RequestOptions {
|
|
310
|
+
readonly ifAvailable?: boolean;
|
|
311
|
+
readonly mode?: LockMode;
|
|
312
|
+
readonly signal?: AbortSignal;
|
|
313
|
+
readonly steal?: boolean;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
interface PendingRequest<T> {
|
|
317
|
+
readonly abort: () => void;
|
|
318
|
+
readonly callback: LockGrantedCallback<T>;
|
|
319
|
+
readonly mode: LockMode;
|
|
320
|
+
readonly name: string;
|
|
321
|
+
phase: "queued" | "granted" | "running" | "settled";
|
|
322
|
+
readonly reject: (reason?: unknown) => void;
|
|
323
|
+
readonly resolve: (value: Awaited<T> | PromiseLike<Awaited<T>>) => void;
|
|
324
|
+
readonly signal?: AbortSignal;
|
|
325
|
+
}
|
package/src/Polyfills.ts
CHANGED
|
@@ -76,10 +76,6 @@ interface AbortControllerPrototype extends AbortController {
|
|
|
76
76
|
readonly __evoluAbortReasonPatched?: true;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
interface AbortSignalWithReason extends AbortSignal {
|
|
80
|
-
readonly reason: unknown;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
79
|
interface AnyControllersRegistry {
|
|
84
80
|
readonly add: (controller: AbortController) => void;
|
|
85
81
|
readonly remove: (controller: AbortController) => void;
|
|
@@ -99,12 +95,12 @@ const anyControllersBySignal = new WeakMap<
|
|
|
99
95
|
|
|
100
96
|
const installAbortControllerPolyfills = (): void => {
|
|
101
97
|
installAbortReasonPolyfill(
|
|
102
|
-
globalThis.AbortController
|
|
103
|
-
globalThis.AbortSignal
|
|
98
|
+
globalThis.AbortController,
|
|
99
|
+
globalThis.AbortSignal,
|
|
104
100
|
);
|
|
105
101
|
installAbortSignalStaticMethods(
|
|
106
|
-
globalThis.AbortController
|
|
107
|
-
globalThis.AbortSignal
|
|
102
|
+
globalThis.AbortController,
|
|
103
|
+
globalThis.AbortSignal,
|
|
108
104
|
);
|
|
109
105
|
};
|
|
110
106
|
|
|
@@ -279,7 +275,7 @@ const createAnyControllersRegistry = (
|
|
|
279
275
|
|
|
280
276
|
const readSignalReason = (signal: AbortSignal): unknown => {
|
|
281
277
|
if ("reason" in signal) {
|
|
282
|
-
return
|
|
278
|
+
return signal.reason;
|
|
283
279
|
}
|
|
284
280
|
return createAbortError();
|
|
285
281
|
};
|
package/src/Task.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
createUnknownError,
|
|
10
10
|
type CreateRun,
|
|
11
11
|
type Run,
|
|
12
|
-
type
|
|
12
|
+
type RunDefaultDeps,
|
|
13
13
|
} from "@evolu/common";
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -28,16 +28,16 @@ import {
|
|
|
28
28
|
* });
|
|
29
29
|
*
|
|
30
30
|
* await using run = createRun({ console });
|
|
31
|
-
* await using
|
|
31
|
+
* await using disposer = new AsyncDisposableStack();
|
|
32
32
|
*
|
|
33
|
-
*
|
|
33
|
+
* disposer.use(await run.orThrow(startApp()));
|
|
34
34
|
* ```
|
|
35
35
|
*
|
|
36
36
|
* @group React Native Run
|
|
37
37
|
*/
|
|
38
|
-
export const createRun: CreateRun<
|
|
38
|
+
export const createRun: CreateRun<RunDefaultDeps> = <D>(
|
|
39
39
|
deps?: D,
|
|
40
|
-
): Run<
|
|
40
|
+
): Run<RunDefaultDeps & D> => {
|
|
41
41
|
const run = createCommonRun(deps);
|
|
42
42
|
|
|
43
43
|
const console = run.deps.console.child("global");
|
package/src/index.ts
CHANGED
package/src/shared.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createConsole,
|
|
3
3
|
createConsoleStoreOutput,
|
|
4
|
-
|
|
4
|
+
createBroadcastChannel,
|
|
5
|
+
createMessageChannel,
|
|
6
|
+
createMessagePort,
|
|
5
7
|
createRandomBytes,
|
|
6
8
|
createRun,
|
|
9
|
+
createSharedWorker,
|
|
7
10
|
createWebSocket,
|
|
11
|
+
createWorker,
|
|
8
12
|
type ConsoleDep,
|
|
9
13
|
type CreateSqliteDriverDep,
|
|
10
14
|
type ReloadAppDep,
|
|
@@ -15,20 +19,14 @@ import type {
|
|
|
15
19
|
DbWorkerInit,
|
|
16
20
|
EvoluDeps,
|
|
17
21
|
SharedWorkerInput,
|
|
22
|
+
SharedWorkerOutput,
|
|
18
23
|
} from "@evolu/common/local-first";
|
|
19
24
|
import {
|
|
20
25
|
createEvoluDeps as createCommonEvoluDeps,
|
|
21
|
-
startDbWorker,
|
|
22
26
|
initSharedWorker,
|
|
27
|
+
startDbWorker,
|
|
23
28
|
} from "@evolu/common/local-first";
|
|
24
|
-
import {
|
|
25
|
-
createMessageChannel,
|
|
26
|
-
createMessagePort,
|
|
27
|
-
createSharedWorker,
|
|
28
|
-
createWorker,
|
|
29
|
-
} from "./Worker.js";
|
|
30
|
-
|
|
31
|
-
const leaderLock = createInMemoryLeaderLock();
|
|
29
|
+
import { lockManager } from "./LockManager.js";
|
|
32
30
|
|
|
33
31
|
/** Creates Evolu dependencies for React Native. */
|
|
34
32
|
export const createEvoluDeps = (
|
|
@@ -46,10 +44,12 @@ export const createEvoluDeps = (
|
|
|
46
44
|
return createRun({
|
|
47
45
|
console: workerConsole,
|
|
48
46
|
consoleStoreOutputEntry: consoleStoreOutput.entry,
|
|
47
|
+
createBroadcastChannel,
|
|
48
|
+
createMessageChannel,
|
|
49
49
|
createMessagePort,
|
|
50
50
|
createWebSocket,
|
|
51
51
|
createSqliteDriver: deps.createSqliteDriver,
|
|
52
|
-
|
|
52
|
+
lockManager,
|
|
53
53
|
randomBytes: createRandomBytes(),
|
|
54
54
|
});
|
|
55
55
|
};
|
|
@@ -60,7 +60,10 @@ export const createEvoluDeps = (
|
|
|
60
60
|
dbWorkerRun(startDbWorker(self));
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
const sharedWorker = createSharedWorker<
|
|
63
|
+
const sharedWorker = createSharedWorker<
|
|
64
|
+
SharedWorkerInput,
|
|
65
|
+
SharedWorkerOutput
|
|
66
|
+
>((self) => {
|
|
64
67
|
const sharedWorkerRun = createWorkerRun();
|
|
65
68
|
sharedWorkerRun(initSharedWorker(self));
|
|
66
69
|
});
|
|
@@ -68,7 +71,9 @@ export const createEvoluDeps = (
|
|
|
68
71
|
return createCommonEvoluDeps({
|
|
69
72
|
...deps,
|
|
70
73
|
createDbWorker,
|
|
74
|
+
createBroadcastChannel,
|
|
71
75
|
createMessageChannel,
|
|
76
|
+
lockManager,
|
|
72
77
|
reloadApp: deps.reloadApp,
|
|
73
78
|
sharedWorker,
|
|
74
79
|
});
|