@evolu/common 8.0.0-next.4 → 8.0.0-next.5
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/dist/src/Assert.d.ts +5 -0
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +6 -1
- package/dist/src/Error.d.ts +5 -3
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +16 -0
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +2 -1
- package/dist/src/Task.d.ts +95 -92
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +60 -52
- package/dist/src/Test.d.ts +10 -11
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Types.d.ts +0 -8
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +10 -10
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +26 -26
- package/package.json +1 -1
- package/src/Assert.ts +6 -1
- package/src/Error.ts +5 -4
- package/src/Sqlite.ts +2 -1
- package/src/Task.ts +319 -314
- package/src/Test.ts +10 -11
- package/src/Types.ts +0 -9
- package/src/local-first/Db.ts +31 -29
- package/src/local-first/Shared.ts +33 -32
package/src/Test.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type Random,
|
|
18
18
|
type RandomLibDep,
|
|
19
19
|
} from "./Random.js";
|
|
20
|
-
import { createRun, type Run, type
|
|
20
|
+
import { createRun, type Run, type RunDefaultDeps } from "./Task.js";
|
|
21
21
|
import {
|
|
22
22
|
minMillis,
|
|
23
23
|
setTimeout,
|
|
@@ -35,14 +35,14 @@ export type TestCreateId = <B extends string = never>() => [B] extends [never]
|
|
|
35
35
|
/**
|
|
36
36
|
* Cheap deterministic baseline deps for tests.
|
|
37
37
|
*
|
|
38
|
-
* `TestDeps` includes test-friendly replacements for {@link
|
|
39
|
-
* {@link TestConsole} and {@link TestTime}, plus extra helpers commonly
|
|
40
|
-
* tests and fixtures, such as `randomLib` (only that for now).
|
|
38
|
+
* `TestDeps` includes test-friendly replacements for {@link RunDefaultDeps},
|
|
39
|
+
* such as {@link TestConsole} and {@link TestTime}, plus extra helpers commonly
|
|
40
|
+
* useful in tests and fixtures, such as `randomLib` (only that for now).
|
|
41
41
|
*
|
|
42
42
|
* Use it directly for synchronous test setup, fixtures, and helpers. It is also
|
|
43
43
|
* intentionally the base deps used by {@link testCreateRun}.
|
|
44
44
|
*/
|
|
45
|
-
export type TestDeps = Omit<
|
|
45
|
+
export type TestDeps = Omit<RunDefaultDeps, "console" | "time"> &
|
|
46
46
|
TestConsoleDep &
|
|
47
47
|
TestTimeDep &
|
|
48
48
|
RandomLibDep;
|
|
@@ -103,7 +103,7 @@ export const testCreateDeps = (options?: {
|
|
|
103
103
|
* ### Example
|
|
104
104
|
*
|
|
105
105
|
* ```ts
|
|
106
|
-
* //
|
|
106
|
+
* // Default TestTime
|
|
107
107
|
* await using run = testCreateRun();
|
|
108
108
|
* const fiber = run(sleep("1s"));
|
|
109
109
|
* run.deps.time.advance("1s");
|
|
@@ -112,9 +112,7 @@ export const testCreateDeps = (options?: {
|
|
|
112
112
|
* // For a single test, create the dependency using the Run.
|
|
113
113
|
* await using run = testCreateRun();
|
|
114
114
|
* await using foo = await run.orThrow(createFoo());
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* expect(runWithFoo.deps.foo).toBe(foo);
|
|
115
|
+
* await run.orThrow(taskThatNeedsFoo, { foo });
|
|
118
116
|
*
|
|
119
117
|
* // If multiple tests need the same setup, create a disposable helper.
|
|
120
118
|
* // Then `await using setup` disposes everything the helper owns.
|
|
@@ -122,14 +120,15 @@ export const testCreateDeps = (options?: {
|
|
|
122
120
|
* await using disposer = new AsyncDisposableStack();
|
|
123
121
|
* const run = disposer.use(testCreateRun());
|
|
124
122
|
* const foo = disposer.use(await run.orThrow(createFoo()));
|
|
123
|
+
* const runWithFoo = disposer.use(run.create({ ...run.deps, foo }));
|
|
125
124
|
* const disposables = disposer.move();
|
|
126
125
|
*
|
|
127
126
|
* // Return whatever the tests need: a Run with the dependency,
|
|
128
127
|
* // the dependency itself, or both.
|
|
129
128
|
* return {
|
|
130
|
-
* run:
|
|
129
|
+
* run: runWithFoo,
|
|
131
130
|
* foo,
|
|
132
|
-
* [Symbol.asyncDispose]: () =>
|
|
131
|
+
* [Symbol.asyncDispose]: () => disposables.disposeAsync(),
|
|
133
132
|
* };
|
|
134
133
|
* };
|
|
135
134
|
*
|
package/src/Types.ts
CHANGED
|
@@ -359,12 +359,3 @@ export type ExtractType<
|
|
|
359
359
|
TUnion extends { readonly type: TypeName },
|
|
360
360
|
TType extends TUnion["type"],
|
|
361
361
|
> = Extract<TUnion, { readonly type: TType }>;
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Constrains `T` to only contain keys not present in `Existing`.
|
|
365
|
-
*
|
|
366
|
-
* Use as a generic constraint to prevent overriding existing properties.
|
|
367
|
-
*/
|
|
368
|
-
export type NewKeys<T, Existing> = {
|
|
369
|
-
[K in keyof T]: K extends keyof Existing ? never : T[K];
|
|
370
|
-
};
|
package/src/local-first/Db.ts
CHANGED
|
@@ -140,12 +140,11 @@ export const startDbWorker =
|
|
|
140
140
|
async (run) => {
|
|
141
141
|
await using disposer = new AsyncDisposableStack();
|
|
142
142
|
disposer.use(self);
|
|
143
|
-
const
|
|
144
|
-
const { deps } = dbWorkerRun;
|
|
143
|
+
const { deps } = run;
|
|
145
144
|
|
|
146
145
|
let initialized = false;
|
|
147
146
|
|
|
148
|
-
const initMessage = await
|
|
147
|
+
const initMessage = await run.orThrow(
|
|
149
148
|
callback<DbWorkerInit>(({ ok }) => {
|
|
150
149
|
self.onMessage = (message) => {
|
|
151
150
|
assert(!initialized, "DbWorker must be initialized only once");
|
|
@@ -179,14 +178,12 @@ export const startDbWorker =
|
|
|
179
178
|
);
|
|
180
179
|
console.debug("createDbWorker");
|
|
181
180
|
|
|
182
|
-
disposer.use(
|
|
183
|
-
await dbWorkerRun.orThrow(acquireLeaderLock(initMessage.name)),
|
|
184
|
-
);
|
|
181
|
+
disposer.use(await run.orThrow(acquireLeaderLock(initMessage.name)));
|
|
185
182
|
|
|
186
183
|
port.postMessage({ type: "LeaderAcquired", name: initMessage.name });
|
|
187
184
|
|
|
188
185
|
const sqlite = disposer.use(
|
|
189
|
-
await
|
|
186
|
+
await run.orThrow(
|
|
190
187
|
createSqlite(
|
|
191
188
|
initMessage.name,
|
|
192
189
|
initMessage.memoryOnly
|
|
@@ -199,16 +196,17 @@ export const startDbWorker =
|
|
|
199
196
|
|
|
200
197
|
const baseSqliteStorage = createBaseSqliteStorage({
|
|
201
198
|
sqlite,
|
|
202
|
-
...
|
|
199
|
+
...deps,
|
|
203
200
|
});
|
|
204
201
|
|
|
205
202
|
const dbDeps = {
|
|
206
|
-
...
|
|
203
|
+
...deps,
|
|
207
204
|
sqlite,
|
|
208
205
|
sqliteSchema: initMessage.sqliteSchema,
|
|
209
206
|
baseSqliteStorage,
|
|
210
207
|
timestampConfig: { maxDrift: defaultTimestampMaxDrift },
|
|
211
208
|
};
|
|
209
|
+
const dbWorkerRun = disposer.use(run.create());
|
|
212
210
|
|
|
213
211
|
const currentSchema = getEvoluSqliteSchema(dbDeps)();
|
|
214
212
|
const dbIsInitialized = "evolu_version" in currentSchema.tables;
|
|
@@ -230,7 +228,6 @@ export const startDbWorker =
|
|
|
230
228
|
});
|
|
231
229
|
|
|
232
230
|
const disposables = disposer.move();
|
|
233
|
-
const dbWorkerRunWithStorage = dbWorkerRun.addDeps({ storage });
|
|
234
231
|
|
|
235
232
|
port.onMessage = (input) => {
|
|
236
233
|
if (input.type === "Dispose") {
|
|
@@ -330,29 +327,34 @@ export const startDbWorker =
|
|
|
330
327
|
case "ApplySyncMessage": {
|
|
331
328
|
const { owner, inputMessage } = request.message;
|
|
332
329
|
|
|
333
|
-
|
|
334
|
-
|
|
330
|
+
dbWorkerRun<void, never, { readonly storage: ClientStorage }>(
|
|
331
|
+
async (run) => {
|
|
332
|
+
const { storage } = run.deps;
|
|
335
333
|
|
|
336
|
-
|
|
337
|
-
applyProtocolMessageAsClient(inputMessage, {
|
|
338
|
-
writeKey: owner.writeKey,
|
|
339
|
-
}),
|
|
340
|
-
);
|
|
334
|
+
storage.setRequestContext(owner.encryptionKey);
|
|
341
335
|
|
|
342
|
-
|
|
336
|
+
const result = await run(
|
|
337
|
+
applyProtocolMessageAsClient(inputMessage, {
|
|
338
|
+
writeKey: owner.writeKey,
|
|
339
|
+
}),
|
|
340
|
+
);
|
|
343
341
|
|
|
344
|
-
|
|
345
|
-
type: "ForSharedWorker",
|
|
346
|
-
message: {
|
|
347
|
-
type: "ApplySyncMessage",
|
|
348
|
-
ownerId: owner.id,
|
|
349
|
-
didWriteMessages,
|
|
350
|
-
result,
|
|
351
|
-
},
|
|
352
|
-
});
|
|
342
|
+
const didWriteMessages = storage.didWriteMessages();
|
|
353
343
|
|
|
354
|
-
|
|
355
|
-
|
|
344
|
+
postQueuedResponse({
|
|
345
|
+
type: "ForSharedWorker",
|
|
346
|
+
message: {
|
|
347
|
+
type: "ApplySyncMessage",
|
|
348
|
+
ownerId: owner.id,
|
|
349
|
+
didWriteMessages,
|
|
350
|
+
result,
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return ok();
|
|
355
|
+
},
|
|
356
|
+
{ storage },
|
|
357
|
+
);
|
|
356
358
|
break;
|
|
357
359
|
}
|
|
358
360
|
|
|
@@ -11,12 +11,11 @@ import {
|
|
|
11
11
|
shiftFromArray,
|
|
12
12
|
type NonEmptyReadonlyArray,
|
|
13
13
|
} from "../Array.js";
|
|
14
|
-
import { assert, assertNotDisposed } from "../Assert.js";
|
|
14
|
+
import { assert, assertNotAborted, assertNotDisposed } from "../Assert.js";
|
|
15
15
|
import type { Brand } from "../Brand.js";
|
|
16
16
|
import { createCallbacks } from "../Callbacks.js";
|
|
17
17
|
import type { ConsoleEntry, ConsoleLevel } from "../Console.js";
|
|
18
18
|
import type { EncryptionKey } from "../Crypto.js";
|
|
19
|
-
import { exhaustiveCheck } from "../Function.js";
|
|
20
19
|
import { acquireLeaderLock, type LockManagerDep } from "../LockManager.js";
|
|
21
20
|
import { structuralLookup, type StructuralLookupKey } from "../Lookup.js";
|
|
22
21
|
import { createRefCountByKey, type RefCountByKey } from "../RefCount.js";
|
|
@@ -30,7 +29,13 @@ import { ok, type Result } from "../Result.js";
|
|
|
30
29
|
import type { NonEmptyReadonlySet } from "../Set.js";
|
|
31
30
|
import type { SqliteSchema } from "../Sqlite.js";
|
|
32
31
|
import { createStore, type Store } from "../Store.js";
|
|
33
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
AbortError,
|
|
34
|
+
createMutex,
|
|
35
|
+
unabortable,
|
|
36
|
+
type Mutex,
|
|
37
|
+
type Task,
|
|
38
|
+
} from "../Task.js";
|
|
34
39
|
import { type Id, type Name, type Typed } from "../Type.js";
|
|
35
40
|
import type { Callback, ExtractType } from "../Types.js";
|
|
36
41
|
import type { CreateWebSocketDep, WebSocket } from "../WebSocket.js";
|
|
@@ -308,10 +313,12 @@ export const initSharedWorker =
|
|
|
308
313
|
|
|
309
314
|
case "CreateEvolu": {
|
|
310
315
|
void sharedWorkerRun(async (run) => {
|
|
311
|
-
const tenant = await run
|
|
312
|
-
tenantsByName.acquire(message),
|
|
316
|
+
const tenant = await run(
|
|
317
|
+
unabortable(tenantsByName.acquire(message)),
|
|
313
318
|
);
|
|
314
|
-
tenant.
|
|
319
|
+
if (!tenant.ok) return tenant;
|
|
320
|
+
|
|
321
|
+
tenant.value.addInstance(
|
|
315
322
|
message,
|
|
316
323
|
() => void sharedWorkerRun(tenantsByName.release(message)),
|
|
317
324
|
);
|
|
@@ -405,7 +412,8 @@ export const initSharedWorker =
|
|
|
405
412
|
),
|
|
406
413
|
);
|
|
407
414
|
|
|
408
|
-
const sharedWorkerRun = run.create(
|
|
415
|
+
const sharedWorkerRun = run.create({
|
|
416
|
+
...deps,
|
|
409
417
|
postConsoleEntryOrError,
|
|
410
418
|
tabLeaderPortStore,
|
|
411
419
|
transports,
|
|
@@ -414,6 +422,7 @@ export const initSharedWorker =
|
|
|
414
422
|
const tenantsByName = disposer.use(
|
|
415
423
|
await sharedWorkerRun.orThrow(
|
|
416
424
|
createSharedResourceByKey(createEvoluTenant, {
|
|
425
|
+
idleDisposeAfter: "3s",
|
|
417
426
|
lookup: (message) => message.name,
|
|
418
427
|
}),
|
|
419
428
|
),
|
|
@@ -505,8 +514,6 @@ const createEvoluTenant =
|
|
|
505
514
|
callbacks.execute(message.callbackId, message);
|
|
506
515
|
break;
|
|
507
516
|
}
|
|
508
|
-
default:
|
|
509
|
-
exhaustiveCheck(message);
|
|
510
517
|
}
|
|
511
518
|
};
|
|
512
519
|
|
|
@@ -549,9 +556,6 @@ const createEvoluTenant =
|
|
|
549
556
|
case "ForSharedWorker":
|
|
550
557
|
handleResponseForSharedWorker(response);
|
|
551
558
|
break;
|
|
552
|
-
|
|
553
|
-
default:
|
|
554
|
-
exhaustiveCheck(response);
|
|
555
559
|
}
|
|
556
560
|
|
|
557
561
|
// Complete the current queue item and continue with the next one.
|
|
@@ -569,7 +573,16 @@ const createEvoluTenant =
|
|
|
569
573
|
dbWorkerPort = null;
|
|
570
574
|
queueRequestInFlight = false;
|
|
571
575
|
|
|
572
|
-
|
|
576
|
+
// The DbWorker holds this tenant leader lock while it is alive. Tenant
|
|
577
|
+
// disposal sends Dispose, then acquires the same lock to wait until the
|
|
578
|
+
// DbWorker releases it: either because Dispose was delivered or because
|
|
579
|
+
// the hosting tab closed. The wait is unabortable because tenant disposal
|
|
580
|
+
// must finish even after tenantRun receives an abort request.
|
|
581
|
+
const lock = await tenantRun(acquireLeaderLock(name));
|
|
582
|
+
// AbortError here means tenantRun was already disposed before this
|
|
583
|
+
// cleanup could start, violating the disposal ordering above.
|
|
584
|
+
assertNotAborted(lock);
|
|
585
|
+
await using _ = lock.value;
|
|
573
586
|
});
|
|
574
587
|
|
|
575
588
|
disposer.defer(deps.tabLeaderPortStore.subscribe(initDbWorker));
|
|
@@ -652,9 +665,6 @@ const createEvoluTenant =
|
|
|
652
665
|
[response.message.file.buffer],
|
|
653
666
|
);
|
|
654
667
|
break;
|
|
655
|
-
|
|
656
|
-
default:
|
|
657
|
-
exhaustiveCheck(response.message);
|
|
658
668
|
}
|
|
659
669
|
};
|
|
660
670
|
|
|
@@ -698,15 +708,9 @@ const createEvoluTenant =
|
|
|
698
708
|
case "Broadcast":
|
|
699
709
|
case "NoResponse":
|
|
700
710
|
break;
|
|
701
|
-
|
|
702
|
-
default:
|
|
703
|
-
exhaustiveCheck(response.message.result.value);
|
|
704
711
|
}
|
|
705
712
|
}
|
|
706
713
|
break;
|
|
707
|
-
|
|
708
|
-
default:
|
|
709
|
-
exhaustiveCheck(response.message);
|
|
710
714
|
}
|
|
711
715
|
};
|
|
712
716
|
|
|
@@ -812,15 +816,14 @@ const createEvoluTenant =
|
|
|
812
816
|
});
|
|
813
817
|
disposer.use(instance.port);
|
|
814
818
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
819
|
+
// The main-thread Evolu instance holds this per-instance leader lock
|
|
820
|
+
// while it is alive. Acquiring the same lock here means the main
|
|
821
|
+
// thread instance was disposed or its tab closed, so the tenant-side
|
|
822
|
+
// instance must dispose itself.
|
|
823
|
+
void tenantRun(acquireLeaderLock(message.id)).then((lock) => {
|
|
824
|
+
if (!lock.ok) return;
|
|
822
825
|
|
|
823
|
-
disposer.use(lock);
|
|
826
|
+
disposer.use(lock.value);
|
|
824
827
|
return disposer.disposeAsync();
|
|
825
828
|
});
|
|
826
829
|
|
|
@@ -857,8 +860,6 @@ const createEvoluTenant =
|
|
|
857
860
|
);
|
|
858
861
|
break;
|
|
859
862
|
}
|
|
860
|
-
default:
|
|
861
|
-
exhaustiveCheck(message);
|
|
862
863
|
}
|
|
863
864
|
};
|
|
864
865
|
},
|