slopkit 0.1.1 → 0.2.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 -1
- package/README.md +41 -0
- package/dist/client.d.ts +15 -1
- package/dist/client.js +4 -0
- package/dist/conformance/fixtures.d.ts +54 -3
- package/dist/conformance/fixtures.js +4 -4
- package/dist/conformance/legacy/replica-012.d.ts +52 -0
- package/dist/conformance/legacy/replica-012.js +403 -0
- package/dist/conformance/paging-budget.test.d.ts +1 -0
- package/dist/conformance/paging-budget.test.js +101 -0
- package/dist/conformance/paging-fixtures.d.ts +80 -0
- package/dist/conformance/paging-fixtures.js +135 -0
- package/dist/conformance/paging-network.test.d.ts +1 -0
- package/dist/conformance/paging-network.test.js +123 -0
- package/dist/conformance/paging-state.test.d.ts +1 -0
- package/dist/conformance/paging-state.test.js +180 -0
- package/dist/conformance/paging-storage.test.d.ts +1 -0
- package/dist/conformance/paging-storage.test.js +255 -0
- package/dist/conformance/paging.test.d.ts +1 -0
- package/dist/conformance/paging.test.js +209 -0
- package/dist/conformance/pull.d.ts +74 -0
- package/dist/conformance/pull.js +400 -0
- package/dist/conformance/push-batch.test.d.ts +2 -0
- package/dist/conformance/push-batch.test.js +66 -0
- package/dist/server/ledger.d.ts +13 -0
- package/dist/server/ledger.js +18 -0
- package/dist/server/pull.d.ts +85 -0
- package/dist/server/pull.js +233 -0
- package/dist/sync.d.ts +141 -14
- package/dist/sync.js +301 -41
- package/dist/web/replica.js +47 -12
- package/package.json +17 -1
package/dist/web/replica.js
CHANGED
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
* - Rows are read back only under the row shapes that wrote them (each table's name and schema `shape`, kept with the
|
|
12
12
|
* checkpoint): any change to a table or its schema starts the rows over, with no number to bump by hand. The next
|
|
13
13
|
* commit then writes the whole replica, so the disk is repaired, not just the memory. Rows that don't parse are
|
|
14
|
-
* treated the same.
|
|
14
|
+
* treated the same.
|
|
15
|
+
* - A partial copy (a first copy still paging in) keeps its history in the checkpoint, written in the same transaction
|
|
16
|
+
* as its rows, and marks its row shapes as partial: a release from before paging reads it as another release's copy,
|
|
17
|
+
* so it never takes part of a history for all of it. A delta takes the patch's history only when the disk is at the
|
|
18
|
+
* cursor the patch moved from, and a complete copy at a revision is never narrowed to a partial one. The server's facts are not rows: facts an older page kept that no longer fit load as null and
|
|
15
19
|
* never cost the copy; facts that change at the same revision are written too.
|
|
16
20
|
* - A version upgrade starts the replica over (it comes back from the server) but keeps the outbox and the generation:
|
|
17
21
|
* an intent on disk is never erased by an upgrade. One this version can't read stays on disk and is counted.
|
|
@@ -21,12 +25,24 @@
|
|
|
21
25
|
* in-memory one). `interfere` is for tests: it sees each write transaction before it commits and may abort it.
|
|
22
26
|
*/
|
|
23
27
|
import { Replaced } from "../client.js";
|
|
24
|
-
import { check } from "../schema.js";
|
|
28
|
+
import { check, literal, nullable, number, object, string } from "../schema.js";
|
|
25
29
|
import { commitPlan } from "../sync.js";
|
|
26
30
|
import { committed, openDatabase, request } from "./idb.js";
|
|
31
|
+
const COMPLETE = { kind: "complete" };
|
|
32
|
+
const count = () => number({ integer: true, min: 0 });
|
|
33
|
+
const partialSchema = object({
|
|
34
|
+
kind: literal("partial"),
|
|
35
|
+
next: string(),
|
|
36
|
+
through: nullable(string()),
|
|
37
|
+
held: count(),
|
|
38
|
+
total: count(),
|
|
39
|
+
});
|
|
27
40
|
export function indexedReplica(options) {
|
|
28
41
|
const names = options.tables.map((lens) => lens.name);
|
|
29
42
|
const shapes = options.tables.map((lens) => lens.shape).join("\n");
|
|
43
|
+
/** A partial copy's row shapes: foreign to a release from before paging. */
|
|
44
|
+
const partialShapes = `${shapes}\npartial`;
|
|
45
|
+
const ours = (written) => written === shapes || written === partialShapes;
|
|
30
46
|
/** The checkpoint a load found whose rows didn't parse: the next commit at it rewrites the whole replica. */
|
|
31
47
|
let damaged;
|
|
32
48
|
/** The dataset of a copy this page loaded but couldn't read (other row shapes, rows that don't parse): its own to replace. */
|
|
@@ -142,22 +158,26 @@ export function indexedReplica(options) {
|
|
|
142
158
|
}
|
|
143
159
|
/** The checkpoint on disk as this version may trust it: one of other row shapes (or none) holds nothing. */
|
|
144
160
|
const checkpointOf = (value) => {
|
|
161
|
+
const none = { dataset: null, revision: 0, history: COMPLETE };
|
|
145
162
|
if (typeof value !== "object" || value === null) {
|
|
146
|
-
return
|
|
163
|
+
return none;
|
|
147
164
|
}
|
|
148
165
|
const dataset = Reflect.get(value, "dataset");
|
|
149
166
|
const revision = Reflect.get(value, "revision");
|
|
150
|
-
|
|
151
|
-
|
|
167
|
+
const written = Reflect.get(value, "shapes");
|
|
168
|
+
const partial = check(partialSchema, Reflect.get(value, "history"));
|
|
169
|
+
if (written !== shapes && (written !== partialShapes || !partial.ok)) {
|
|
170
|
+
return none;
|
|
152
171
|
}
|
|
153
172
|
const at = {
|
|
154
173
|
dataset: typeof dataset === "string" ? dataset : null,
|
|
155
174
|
revision: typeof revision === "number" ? revision : 0,
|
|
175
|
+
history: written === partialShapes && partial.ok ? partial.value : COMPLETE,
|
|
156
176
|
};
|
|
157
177
|
if (damaged !== undefined &&
|
|
158
178
|
damaged.dataset === at.dataset &&
|
|
159
179
|
damaged.revision === at.revision) {
|
|
160
|
-
return
|
|
180
|
+
return none;
|
|
161
181
|
}
|
|
162
182
|
return at;
|
|
163
183
|
};
|
|
@@ -197,6 +217,7 @@ export function indexedReplica(options) {
|
|
|
197
217
|
revision: 0,
|
|
198
218
|
tables: options.empty,
|
|
199
219
|
server: null,
|
|
220
|
+
history: COMPLETE,
|
|
200
221
|
};
|
|
201
222
|
const at = checkpointOf(point);
|
|
202
223
|
const raw = typeof point === "object" && point !== null ? Reflect.get(point, "dataset") : null;
|
|
@@ -232,6 +253,7 @@ export function indexedReplica(options) {
|
|
|
232
253
|
revision: at.revision,
|
|
233
254
|
tables: restored,
|
|
234
255
|
server: server?.ok === true ? server.value : null,
|
|
256
|
+
history: at.history,
|
|
235
257
|
},
|
|
236
258
|
commands,
|
|
237
259
|
unreadable,
|
|
@@ -246,7 +268,6 @@ export function indexedReplica(options) {
|
|
|
246
268
|
dataset: commit.replica.dataset,
|
|
247
269
|
revision: commit.replica.revision,
|
|
248
270
|
server: commit.replica.server,
|
|
249
|
-
shapes,
|
|
250
271
|
};
|
|
251
272
|
try {
|
|
252
273
|
await guard(transaction);
|
|
@@ -267,6 +288,7 @@ export function indexedReplica(options) {
|
|
|
267
288
|
reset: commit.reset,
|
|
268
289
|
base: commit.base,
|
|
269
290
|
replaces: commit.replaces,
|
|
291
|
+
...(commit.from === undefined ? {} : { from: commit.from }),
|
|
270
292
|
});
|
|
271
293
|
// The disk holds a dataset this page never saw: the server replaced the one this page is on.
|
|
272
294
|
if (plan === "stale") {
|
|
@@ -278,7 +300,7 @@ export function indexedReplica(options) {
|
|
|
278
300
|
// Another release's tab wrote this dataset under its own row shapes since this page loaded: that release owns
|
|
279
301
|
// the copy now, at any revision, and this page stops writing until it reloads.
|
|
280
302
|
if (held === commit.replica.dataset &&
|
|
281
|
-
written
|
|
303
|
+
!ours(written) &&
|
|
282
304
|
(loaded === undefined ||
|
|
283
305
|
loaded.dataset !== held ||
|
|
284
306
|
loaded.revision !== revision ||
|
|
@@ -305,7 +327,7 @@ export function indexedReplica(options) {
|
|
|
305
327
|
}
|
|
306
328
|
}
|
|
307
329
|
};
|
|
308
|
-
if (plan === "delta") {
|
|
330
|
+
if (plan === "delta" || plan === "adopt") {
|
|
309
331
|
write(commit.ops);
|
|
310
332
|
}
|
|
311
333
|
if (plan === "whole") {
|
|
@@ -318,8 +340,16 @@ export function indexedReplica(options) {
|
|
|
318
340
|
}
|
|
319
341
|
// At the same revision the rows are already there, but the server's facts may have moved: they are kept too.
|
|
320
342
|
const facts = plan === "skip" && stored.revision === target.revision;
|
|
343
|
+
// A delta keeps the disk's history; a whole write or a delta from the disk's own cursor takes the page's.
|
|
344
|
+
const history = plan === "whole" || plan === "adopt"
|
|
345
|
+
? (commit.replica.history ?? COMPLETE)
|
|
346
|
+
: stored.history;
|
|
347
|
+
const checkpoint = {
|
|
348
|
+
...target,
|
|
349
|
+
...(history.kind === "partial" ? { history, shapes: partialShapes } : { shapes }),
|
|
350
|
+
};
|
|
321
351
|
if (plan !== "skip" || facts) {
|
|
322
|
-
meta.put(
|
|
352
|
+
meta.put(checkpoint, "checkpoint");
|
|
323
353
|
}
|
|
324
354
|
options.interfere?.("commit", transaction);
|
|
325
355
|
await done;
|
|
@@ -328,11 +358,16 @@ export function indexedReplica(options) {
|
|
|
328
358
|
discarded = null;
|
|
329
359
|
loaded = undefined;
|
|
330
360
|
}
|
|
331
|
-
const now = plan === "skip" ? stored :
|
|
361
|
+
const now = plan === "skip" ? stored : checkpoint;
|
|
332
362
|
if (now.dataset === null) {
|
|
333
363
|
throw new Error("the replica has no dataset");
|
|
334
364
|
}
|
|
335
|
-
|
|
365
|
+
// A delta under the disk's own history leaves out rows of this page's copy: said, so a failed write stays owed.
|
|
366
|
+
return {
|
|
367
|
+
dataset: now.dataset,
|
|
368
|
+
revision: now.revision,
|
|
369
|
+
...(plan === "delta" && stored.history.kind === "partial" ? { whole: false } : {}),
|
|
370
|
+
};
|
|
336
371
|
}
|
|
337
372
|
catch (error) {
|
|
338
373
|
await done.catch(() => undefined);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slopkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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",
|
|
@@ -82,6 +82,10 @@
|
|
|
82
82
|
"types": "./dist/server/jobs.d.ts",
|
|
83
83
|
"default": "./dist/server/jobs.js"
|
|
84
84
|
},
|
|
85
|
+
"./server/pull": {
|
|
86
|
+
"types": "./dist/server/pull.d.ts",
|
|
87
|
+
"default": "./dist/server/pull.js"
|
|
88
|
+
},
|
|
85
89
|
"./server/web": {
|
|
86
90
|
"types": "./dist/server/web.d.ts",
|
|
87
91
|
"default": "./dist/server/web.js"
|
|
@@ -118,6 +122,18 @@
|
|
|
118
122
|
"types": "./dist/conformance/fixtures.d.ts",
|
|
119
123
|
"default": "./dist/conformance/fixtures.js"
|
|
120
124
|
},
|
|
125
|
+
"./conformance/legacy/replica-012": {
|
|
126
|
+
"types": "./dist/conformance/legacy/replica-012.d.ts",
|
|
127
|
+
"default": "./dist/conformance/legacy/replica-012.js"
|
|
128
|
+
},
|
|
129
|
+
"./conformance/paging-fixtures": {
|
|
130
|
+
"types": "./dist/conformance/paging-fixtures.d.ts",
|
|
131
|
+
"default": "./dist/conformance/paging-fixtures.js"
|
|
132
|
+
},
|
|
133
|
+
"./conformance/pull": {
|
|
134
|
+
"types": "./dist/conformance/pull.d.ts",
|
|
135
|
+
"default": "./dist/conformance/pull.js"
|
|
136
|
+
},
|
|
121
137
|
"./package.json": "./package.json"
|
|
122
138
|
},
|
|
123
139
|
"dependencies": {
|