@convex-localfirst/core 0.1.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/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/collection.d.ts +101 -0
- package/dist/collection.js +100 -0
- package/dist/declarative.d.ts +56 -0
- package/dist/declarative.js +86 -0
- package/dist/engine.d.ts +237 -0
- package/dist/engine.js +934 -0
- package/dist/functionName.d.ts +3 -0
- package/dist/functionName.js +15 -0
- package/dist/id.d.ts +5 -0
- package/dist/id.js +22 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +27 -0
- package/dist/indexedDbStore.d.ts +53 -0
- package/dist/indexedDbStore.js +328 -0
- package/dist/internal.d.ts +12 -0
- package/dist/internal.js +22 -0
- package/dist/leadership.d.ts +48 -0
- package/dist/leadership.js +69 -0
- package/dist/manifest.d.ts +84 -0
- package/dist/manifest.js +28 -0
- package/dist/memoryStore.d.ts +33 -0
- package/dist/memoryStore.js +130 -0
- package/dist/multiTab.d.ts +69 -0
- package/dist/multiTab.js +96 -0
- package/dist/mutationCall.d.ts +20 -0
- package/dist/mutationCall.js +40 -0
- package/dist/ordering.d.ts +14 -0
- package/dist/ordering.js +35 -0
- package/dist/rebase.d.ts +14 -0
- package/dist/rebase.js +54 -0
- package/dist/relations.d.ts +42 -0
- package/dist/relations.js +89 -0
- package/dist/setMerge.d.ts +63 -0
- package/dist/setMerge.js +93 -0
- package/dist/status.d.ts +2 -0
- package/dist/status.js +10 -0
- package/dist/storage.d.ts +53 -0
- package/dist/storage.js +1 -0
- package/dist/transport.d.ts +43 -0
- package/dist/transport.js +93 -0
- package/dist/types.d.ts +173 -0
- package/dist/types.js +1 -0
- package/dist/view.d.ts +12 -0
- package/dist/view.js +74 -0
- package/package.json +42 -0
package/dist/view.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { compareOperations } from "./ordering.js";
|
|
2
|
+
import { rebaseAndReplay } from "./rebase.js";
|
|
3
|
+
/** Ops whose effect is not yet folded into canonical, so they must be replayed. */
|
|
4
|
+
const REPLAYED_STATUSES = new Set(["pending", "pushing", "acked"]);
|
|
5
|
+
/**
|
|
6
|
+
* Derive the live view for one table (Invariant I1): canonical snapshot with the
|
|
7
|
+
* deterministically-ordered pending operations replayed on top. Shared by every
|
|
8
|
+
* LocalStore implementation so the rebase logic lives in exactly one place.
|
|
9
|
+
*/
|
|
10
|
+
export function deriveView(table, canonicalRows, operations) {
|
|
11
|
+
const ops = operations.filter((operation) => operation.table === table);
|
|
12
|
+
const replayed = ops.filter((operation) => REPLAYED_STATUSES.has(operation.status)).sort(compareOperations);
|
|
13
|
+
const { rows, conflicts } = rebaseAndReplay({
|
|
14
|
+
canonicalRows,
|
|
15
|
+
serverChanges: [],
|
|
16
|
+
pendingOperations: replayed
|
|
17
|
+
});
|
|
18
|
+
const byId = new Map(rows.map((row) => [row._id, row]));
|
|
19
|
+
// Replay failures (e.g. patch over a missing row) surface as row conflicts.
|
|
20
|
+
for (const conflict of conflicts) {
|
|
21
|
+
const op = ops.find((operation) => operation.opId === conflict.opId);
|
|
22
|
+
const row = op ? byId.get(op.id) : undefined;
|
|
23
|
+
if (row) {
|
|
24
|
+
row._conflict = { kind: "mergeFailed", message: conflict.message, opId: conflict.opId };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Server rejections surface on the (now reverted) canonical row. A rejected
|
|
28
|
+
// INSERT has no canonical row to annotate — the optimistic row correctly
|
|
29
|
+
// reverts (the insert never happened); the rejection is still observable via
|
|
30
|
+
// the op's "rejected" status and status.lastError. ponytail: surfacing a
|
|
31
|
+
// dismissable "ghost" row for a rejected insert is a deferred UX enhancement.
|
|
32
|
+
for (const op of ops) {
|
|
33
|
+
if (op.status !== "rejected") {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const row = byId.get(op.id);
|
|
37
|
+
if (row) {
|
|
38
|
+
row._conflict = { kind: "serverRejected", message: op.error ?? "Server rejected the operation", opId: op.opId };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return Array.from(byId.values());
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Compute the next canonical row for a server change, or "stale" if the change
|
|
45
|
+
* must be ignored because its version does not advance the row (Invariant I5).
|
|
46
|
+
*/
|
|
47
|
+
export function nextCanonicalRow(current, change) {
|
|
48
|
+
if (current && typeof current._version === "number" && change.version <= current._version) {
|
|
49
|
+
return "stale";
|
|
50
|
+
}
|
|
51
|
+
if (change.kind === "delete") {
|
|
52
|
+
const base = current ?? { _id: change.id, _table: change.table };
|
|
53
|
+
return { ...base, _id: change.id, _table: change.table, _deleted: true, _version: change.version };
|
|
54
|
+
}
|
|
55
|
+
if (change.kind === "patch") {
|
|
56
|
+
const base = current ?? { _id: change.id, _table: change.table };
|
|
57
|
+
return {
|
|
58
|
+
...base,
|
|
59
|
+
...(change.patch ?? {}),
|
|
60
|
+
_id: change.id,
|
|
61
|
+
_table: change.table,
|
|
62
|
+
_version: change.version,
|
|
63
|
+
_deleted: false
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// insert | replace: the canonical row becomes exactly the server value.
|
|
67
|
+
return {
|
|
68
|
+
...(change.value ?? {}),
|
|
69
|
+
_id: change.id,
|
|
70
|
+
_table: change.table,
|
|
71
|
+
_version: change.version,
|
|
72
|
+
_deleted: false
|
|
73
|
+
};
|
|
74
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@convex-localfirst/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local-first engine, store contract, and sync protocol for Convex — optimistic writes, offline cache, rebase/replay.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"convex",
|
|
8
|
+
"local-first",
|
|
9
|
+
"offline",
|
|
10
|
+
"sync",
|
|
11
|
+
"optimistic"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./internal": {
|
|
22
|
+
"types": "./dist/internal.d.ts",
|
|
23
|
+
"import": "./dist/internal.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"fake-indexeddb": "^6.2.5",
|
|
34
|
+
"typescript": "^5.7.0",
|
|
35
|
+
"vitest": "^2.1.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -p tsconfig.json --noEmit false --emitDeclarationOnly false",
|
|
39
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
40
|
+
"test": "vitest run"
|
|
41
|
+
}
|
|
42
|
+
}
|