@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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/collection.d.ts +101 -0
  4. package/dist/collection.js +100 -0
  5. package/dist/declarative.d.ts +56 -0
  6. package/dist/declarative.js +86 -0
  7. package/dist/engine.d.ts +237 -0
  8. package/dist/engine.js +934 -0
  9. package/dist/functionName.d.ts +3 -0
  10. package/dist/functionName.js +15 -0
  11. package/dist/id.d.ts +5 -0
  12. package/dist/id.js +22 -0
  13. package/dist/index.d.ts +14 -0
  14. package/dist/index.js +27 -0
  15. package/dist/indexedDbStore.d.ts +53 -0
  16. package/dist/indexedDbStore.js +328 -0
  17. package/dist/internal.d.ts +12 -0
  18. package/dist/internal.js +22 -0
  19. package/dist/leadership.d.ts +48 -0
  20. package/dist/leadership.js +69 -0
  21. package/dist/manifest.d.ts +84 -0
  22. package/dist/manifest.js +28 -0
  23. package/dist/memoryStore.d.ts +33 -0
  24. package/dist/memoryStore.js +130 -0
  25. package/dist/multiTab.d.ts +69 -0
  26. package/dist/multiTab.js +96 -0
  27. package/dist/mutationCall.d.ts +20 -0
  28. package/dist/mutationCall.js +40 -0
  29. package/dist/ordering.d.ts +14 -0
  30. package/dist/ordering.js +35 -0
  31. package/dist/rebase.d.ts +14 -0
  32. package/dist/rebase.js +54 -0
  33. package/dist/relations.d.ts +42 -0
  34. package/dist/relations.js +89 -0
  35. package/dist/setMerge.d.ts +63 -0
  36. package/dist/setMerge.js +93 -0
  37. package/dist/status.d.ts +2 -0
  38. package/dist/status.js +10 -0
  39. package/dist/storage.d.ts +53 -0
  40. package/dist/storage.js +1 -0
  41. package/dist/transport.d.ts +43 -0
  42. package/dist/transport.js +93 -0
  43. package/dist/types.d.ts +173 -0
  44. package/dist/types.js +1 -0
  45. package/dist/view.d.ts +12 -0
  46. package/dist/view.js +74 -0
  47. 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
+ }