slopkit 0.2.0 → 0.3.0
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/DEPS.md +2 -2
- package/README.md +10 -0
- package/dist/client.d.ts +16 -3
- package/dist/client.js +21 -2
- package/dist/conformance/client.test.js +7 -7
- package/dist/conformance/fixtures.d.ts +1 -0
- package/dist/conformance/fixtures.js +5 -1
- package/dist/conformance/outbox-bound.test.d.ts +1 -0
- package/dist/conformance/outbox-bound.test.js +268 -0
- package/dist/conformance/push-bytes.test.d.ts +1 -0
- package/dist/conformance/push-bytes.test.js +174 -0
- package/dist/conformance/push-refusal.test.d.ts +1 -0
- package/dist/conformance/push-refusal.test.js +644 -0
- package/dist/contract.d.ts +19 -6
- package/dist/contract.js +76 -9
- package/dist/server/pull.js +158 -67
- package/dist/server/web.d.ts +9 -5
- package/dist/server/web.js +29 -12
- package/dist/sync.d.ts +84 -2
- package/dist/sync.js +276 -59
- package/dist/web/replica.js +46 -4
- package/package.json +1 -1
package/dist/web/replica.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* replica instead, so the checkpoint never covers rows the disk doesn't have.
|
|
8
8
|
* - Every write checks the database's generation, read at load: after a forget in another tab this page's writes
|
|
9
9
|
* fail as `Replaced` instead of bringing the forgotten copy back.
|
|
10
|
-
* - The outbox is written per command, never replaced whole, so tabs can't overwrite each other's intents.
|
|
10
|
+
* - The outbox is written per command, never replaced whole, so tabs can't overwrite each other's intents. A new
|
|
11
|
+
* command is admitted inside its own write's transaction, from the store's count, so every tab shares one bound;
|
|
12
|
+
* a kept one always updates, and nothing kept is dropped for room.
|
|
11
13
|
* - Rows are read back only under the row shapes that wrote them (each table's name and schema `shape`, kept with the
|
|
12
14
|
* checkpoint): any change to a table or its schema starts the rows over, with no number to bump by hand. The next
|
|
13
15
|
* commit then writes the whole replica, so the disk is repaired, not just the memory. Rows that don't parse are
|
|
@@ -26,7 +28,7 @@
|
|
|
26
28
|
*/
|
|
27
29
|
import { Replaced } from "../client.js";
|
|
28
30
|
import { check, literal, nullable, number, object, string } from "../schema.js";
|
|
29
|
-
import { commitPlan } from "../sync.js";
|
|
31
|
+
import { commitPlan, OUTBOX_MAX } from "../sync.js";
|
|
30
32
|
import { committed, openDatabase, request } from "./idb.js";
|
|
31
33
|
const COMPLETE = { kind: "complete" };
|
|
32
34
|
const count = () => number({ integer: true, min: 0 });
|
|
@@ -374,15 +376,31 @@ export function indexedReplica(options) {
|
|
|
374
376
|
throw error;
|
|
375
377
|
}
|
|
376
378
|
},
|
|
377
|
-
async keep(pending) {
|
|
379
|
+
async keep(pending, max = OUTBOX_MAX) {
|
|
378
380
|
const database = await db();
|
|
379
381
|
const transaction = database.transaction(["meta", "outbox"], "readwrite");
|
|
380
382
|
const done = committed(transaction);
|
|
383
|
+
const outbox = transaction.objectStore("outbox");
|
|
384
|
+
const key = [pending.client, pending.id];
|
|
381
385
|
try {
|
|
382
386
|
await guard(transaction);
|
|
383
|
-
|
|
387
|
+
// Admission is decided here, from the store's own count, so every tab shares one bound. A kept key always
|
|
388
|
+
// updates; records already past the bound (an older release, a larger one) stay, and keep new ones out.
|
|
389
|
+
if ((await request(outbox.count(key))) === 0 &&
|
|
390
|
+
(await request(outbox.count())) >= max) {
|
|
391
|
+
const kept = await request(outbox.getAll());
|
|
392
|
+
// Unreadable records take room too: counted as waiting, since what they hold is unknown.
|
|
393
|
+
const finished = kept.filter((each) => {
|
|
394
|
+
const parsed = check(options.pending, each);
|
|
395
|
+
return parsed.ok && parsed.value.state.kind === "done";
|
|
396
|
+
}).length;
|
|
397
|
+
await done;
|
|
398
|
+
return { kind: "full", waiting: kept.length - finished, finished, max };
|
|
399
|
+
}
|
|
400
|
+
outbox.put(pending, key);
|
|
384
401
|
options.interfere?.("keep", transaction);
|
|
385
402
|
await done;
|
|
403
|
+
return undefined;
|
|
386
404
|
}
|
|
387
405
|
catch (error) {
|
|
388
406
|
await done.catch(() => undefined);
|
|
@@ -406,6 +424,30 @@ export function indexedReplica(options) {
|
|
|
406
424
|
throw error;
|
|
407
425
|
}
|
|
408
426
|
},
|
|
427
|
+
async mark(keys) {
|
|
428
|
+
const database = await db();
|
|
429
|
+
const transaction = database.transaction(["meta", "outbox"], "readwrite");
|
|
430
|
+
const done = committed(transaction);
|
|
431
|
+
const outbox = transaction.objectStore("outbox");
|
|
432
|
+
try {
|
|
433
|
+
await guard(transaction);
|
|
434
|
+
const kept = await Promise.all(keys.map((key) => request(outbox.get([key.client, key.id]))));
|
|
435
|
+
// A push's commands, in flight, in one write: only records kept already, so nothing is admitted here.
|
|
436
|
+
for (const [at, record] of kept.entries()) {
|
|
437
|
+
const key = keys[at];
|
|
438
|
+
if (key !== undefined && typeof record === "object" && record !== null) {
|
|
439
|
+
outbox.put({ ...record, state: { kind: "sending" } }, [key.client, key.id]);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// An outbox write, as a keep is.
|
|
443
|
+
options.interfere?.("keep", transaction);
|
|
444
|
+
await done;
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
await done.catch(() => undefined);
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
},
|
|
409
451
|
/** Drop this device's copy and its outbox, and move to a new generation so no open tab writes it back. */
|
|
410
452
|
async forget() {
|
|
411
453
|
const database = await db();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slopkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Opinionated standards and libraries for fast, local-first personal software: state machines, exhaustive state tables, sync, a command ledger, jobs, a guarded server, and the conformance suite that proves them",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"conformance",
|