@graview/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 +96 -0
- package/README.md +35 -0
- package/dist/app.d.ts +294 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +49 -0
- package/dist/app.js.map +1 -0
- package/dist/arrange.d.ts +185 -0
- package/dist/arrange.d.ts.map +1 -0
- package/dist/arrange.js +456 -0
- package/dist/arrange.js.map +1 -0
- package/dist/beginning.d.ts +65 -0
- package/dist/beginning.d.ts.map +1 -0
- package/dist/beginning.js +94 -0
- package/dist/beginning.js.map +1 -0
- package/dist/bind.d.ts +42 -0
- package/dist/bind.d.ts.map +1 -0
- package/dist/bind.js +26 -0
- package/dist/bind.js.map +1 -0
- package/dist/city.d.ts +93 -0
- package/dist/city.d.ts.map +1 -0
- package/dist/city.js +160 -0
- package/dist/city.js.map +1 -0
- package/dist/cli/check/arrangement.d.ts +13 -0
- package/dist/cli/check/arrangement.d.ts.map +1 -0
- package/dist/cli/check/arrangement.js +63 -0
- package/dist/cli/check/arrangement.js.map +1 -0
- package/dist/cli/check/beginnings.d.ts +16 -0
- package/dist/cli/check/beginnings.d.ts.map +1 -0
- package/dist/cli/check/beginnings.js +53 -0
- package/dist/cli/check/beginnings.js.map +1 -0
- package/dist/cli/check/brand.d.ts +17 -0
- package/dist/cli/check/brand.d.ts.map +1 -0
- package/dist/cli/check/brand.js +153 -0
- package/dist/cli/check/brand.js.map +1 -0
- package/dist/cli/check/city.d.ts +17 -0
- package/dist/cli/check/city.d.ts.map +1 -0
- package/dist/cli/check/city.js +58 -0
- package/dist/cli/check/city.js.map +1 -0
- package/dist/cli/check/context.d.ts +50 -0
- package/dist/cli/check/context.d.ts.map +1 -0
- package/dist/cli/check/context.js +37 -0
- package/dist/cli/check/context.js.map +1 -0
- package/dist/cli/check/fields.d.ts +16 -0
- package/dist/cli/check/fields.d.ts.map +1 -0
- package/dist/cli/check/fields.js +237 -0
- package/dist/cli/check/fields.js.map +1 -0
- package/dist/cli/check/index.d.ts +14 -0
- package/dist/cli/check/index.d.ts.map +1 -0
- package/dist/cli/check/index.js +14 -0
- package/dist/cli/check/index.js.map +1 -0
- package/dist/cli/check/intelligence.d.ts +17 -0
- package/dist/cli/check/intelligence.d.ts.map +1 -0
- package/dist/cli/check/intelligence.js +136 -0
- package/dist/cli/check/intelligence.js.map +1 -0
- package/dist/cli/check/lenses.d.ts +17 -0
- package/dist/cli/check/lenses.d.ts.map +1 -0
- package/dist/cli/check/lenses.js +239 -0
- package/dist/cli/check/lenses.js.map +1 -0
- package/dist/cli/check/migrations.d.ts +16 -0
- package/dist/cli/check/migrations.d.ts.map +1 -0
- package/dist/cli/check/migrations.js +43 -0
- package/dist/cli/check/migrations.js.map +1 -0
- package/dist/cli/check/modules.d.ts +16 -0
- package/dist/cli/check/modules.d.ts.map +1 -0
- package/dist/cli/check/modules.js +123 -0
- package/dist/cli/check/modules.js.map +1 -0
- package/dist/cli/check/policy.d.ts +16 -0
- package/dist/cli/check/policy.d.ts.map +1 -0
- package/dist/cli/check/policy.js +90 -0
- package/dist/cli/check/policy.js.map +1 -0
- package/dist/cli/check/relations.d.ts +29 -0
- package/dist/cli/check/relations.d.ts.map +1 -0
- package/dist/cli/check/relations.js +279 -0
- package/dist/cli/check/relations.js.map +1 -0
- package/dist/cli/check/routes.d.ts +16 -0
- package/dist/cli/check/routes.d.ts.map +1 -0
- package/dist/cli/check/routes.js +31 -0
- package/dist/cli/check/routes.js.map +1 -0
- package/dist/cli/check/settings.d.ts +16 -0
- package/dist/cli/check/settings.d.ts.map +1 -0
- package/dist/cli/check/settings.js +81 -0
- package/dist/cli/check/settings.js.map +1 -0
- package/dist/cli/check.d.ts +36 -0
- package/dist/cli/check.d.ts.map +1 -0
- package/dist/cli/check.js +76 -0
- package/dist/cli/check.js.map +1 -0
- package/dist/cli/create.d.ts +20 -0
- package/dist/cli/create.d.ts.map +1 -0
- package/dist/cli/create.js +251 -0
- package/dist/cli/create.js.map +1 -0
- package/dist/cli/describe.d.ts +35 -0
- package/dist/cli/describe.d.ts.map +1 -0
- package/dist/cli/describe.js +230 -0
- package/dist/cli/describe.js.map +1 -0
- package/dist/cli/docs.d.ts +11 -0
- package/dist/cli/docs.d.ts.map +1 -0
- package/dist/cli/docs.js +152 -0
- package/dist/cli/docs.js.map +1 -0
- package/dist/cli/index.d.ts +18 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +281 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/extend.d.ts +23 -0
- package/dist/extend.d.ts.map +1 -0
- package/dist/extend.js +53 -0
- package/dist/extend.js.map +1 -0
- package/dist/graph/diff.d.ts +31 -0
- package/dist/graph/diff.d.ts.map +1 -0
- package/dist/graph/diff.js +71 -0
- package/dist/graph/diff.js.map +1 -0
- package/dist/graph/graph.d.ts +69 -0
- package/dist/graph/graph.d.ts.map +1 -0
- package/dist/graph/graph.js +284 -0
- package/dist/graph/graph.js.map +1 -0
- package/dist/graph/primitives.d.ts +52 -0
- package/dist/graph/primitives.d.ts.map +1 -0
- package/dist/graph/primitives.js +62 -0
- package/dist/graph/primitives.js.map +1 -0
- package/dist/graph/tracked.d.ts +31 -0
- package/dist/graph/tracked.d.ts.map +1 -0
- package/dist/graph/tracked.js +69 -0
- package/dist/graph/tracked.js.map +1 -0
- package/dist/graph/types.d.ts +34 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +0 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +90 -0
- package/dist/index.js.map +1 -0
- package/dist/installation.d.ts +112 -0
- package/dist/installation.d.ts.map +1 -0
- package/dist/installation.js +193 -0
- package/dist/installation.js.map +1 -0
- package/dist/intelligence-bridge.d.ts +73 -0
- package/dist/intelligence-bridge.d.ts.map +1 -0
- package/dist/intelligence-bridge.js +31 -0
- package/dist/intelligence-bridge.js.map +1 -0
- package/dist/invariants/engine.d.ts +36 -0
- package/dist/invariants/engine.d.ts.map +1 -0
- package/dist/invariants/engine.js +88 -0
- package/dist/invariants/engine.js.map +1 -0
- package/dist/invariants/types.d.ts +75 -0
- package/dist/invariants/types.d.ts.map +1 -0
- package/dist/invariants/types.js +2 -0
- package/dist/invariants/types.js.map +1 -0
- package/dist/modules.d.ts +75 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +87 -0
- package/dist/modules.js.map +1 -0
- package/dist/mutations/decidable.d.ts +26 -0
- package/dist/mutations/decidable.d.ts.map +1 -0
- package/dist/mutations/decidable.js +39 -0
- package/dist/mutations/decidable.js.map +1 -0
- package/dist/mutations/define-mutation.d.ts +32 -0
- package/dist/mutations/define-mutation.d.ts.map +1 -0
- package/dist/mutations/define-mutation.js +181 -0
- package/dist/mutations/define-mutation.js.map +1 -0
- package/dist/mutations/derive-edits.d.ts +75 -0
- package/dist/mutations/derive-edits.d.ts.map +1 -0
- package/dist/mutations/derive-edits.js +240 -0
- package/dist/mutations/derive-edits.js.map +1 -0
- package/dist/mutations/form.d.ts +63 -0
- package/dist/mutations/form.d.ts.map +1 -0
- package/dist/mutations/form.js +113 -0
- package/dist/mutations/form.js.map +1 -0
- package/dist/mutations/node-ref.d.ts +83 -0
- package/dist/mutations/node-ref.d.ts.map +1 -0
- package/dist/mutations/node-ref.js +113 -0
- package/dist/mutations/node-ref.js.map +1 -0
- package/dist/mutations/types.d.ts +136 -0
- package/dist/mutations/types.d.ts.map +1 -0
- package/dist/mutations/types.js +2 -0
- package/dist/mutations/types.js.map +1 -0
- package/dist/ops/log.d.ts +35 -0
- package/dist/ops/log.d.ts.map +1 -0
- package/dist/ops/log.js +103 -0
- package/dist/ops/log.js.map +1 -0
- package/dist/ops/types.d.ts +52 -0
- package/dist/ops/types.d.ts.map +1 -0
- package/dist/ops/types.js +2 -0
- package/dist/ops/types.js.map +1 -0
- package/dist/ops/undo.d.ts +33 -0
- package/dist/ops/undo.d.ts.map +1 -0
- package/dist/ops/undo.js +65 -0
- package/dist/ops/undo.js.map +1 -0
- package/dist/permissions/policy.d.ts +68 -0
- package/dist/permissions/policy.d.ts.map +1 -0
- package/dist/permissions/policy.js +191 -0
- package/dist/permissions/policy.js.map +1 -0
- package/dist/permissions/types.d.ts +78 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/permissions/types.js +17 -0
- package/dist/permissions/types.js.map +1 -0
- package/dist/persistence/memory.d.ts +6 -0
- package/dist/persistence/memory.d.ts.map +1 -0
- package/dist/persistence/memory.js +31 -0
- package/dist/persistence/memory.js.map +1 -0
- package/dist/persistence/sqlite.d.ts +59 -0
- package/dist/persistence/sqlite.d.ts.map +1 -0
- package/dist/persistence/sqlite.js +131 -0
- package/dist/persistence/sqlite.js.map +1 -0
- package/dist/persistence/types.d.ts +22 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +2 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/presence.d.ts +38 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +50 -0
- package/dist/presence.js.map +1 -0
- package/dist/scaffold/domain.d.ts +8 -0
- package/dist/scaffold/domain.d.ts.map +1 -0
- package/dist/scaffold/domain.js +331 -0
- package/dist/scaffold/domain.js.map +1 -0
- package/dist/scaffold/index.d.ts +66 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +142 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/lens.d.ts +31 -0
- package/dist/scaffold/lens.d.ts.map +1 -0
- package/dist/scaffold/lens.js +176 -0
- package/dist/scaffold/lens.js.map +1 -0
- package/dist/scaffold/names.d.ts +44 -0
- package/dist/scaffold/names.d.ts.map +1 -0
- package/dist/scaffold/names.js +69 -0
- package/dist/scaffold/names.js.map +1 -0
- package/dist/scaffold/project.d.ts +41 -0
- package/dist/scaffold/project.d.ts.map +1 -0
- package/dist/scaffold/project.js +604 -0
- package/dist/scaffold/project.js.map +1 -0
- package/dist/scaffold/ui.d.ts +15 -0
- package/dist/scaffold/ui.d.ts.map +1 -0
- package/dist/scaffold/ui.js +536 -0
- package/dist/scaffold/ui.js.map +1 -0
- package/dist/schema/define-node.d.ts +132 -0
- package/dist/schema/define-node.d.ts.map +1 -0
- package/dist/schema/define-node.js +254 -0
- package/dist/schema/define-node.js.map +1 -0
- package/dist/schema/figures.d.ts +42 -0
- package/dist/schema/figures.d.ts.map +1 -0
- package/dist/schema/figures.js +220 -0
- package/dist/schema/figures.js.map +1 -0
- package/dist/schema/json-schema.d.ts +39 -0
- package/dist/schema/json-schema.d.ts.map +1 -0
- package/dist/schema/json-schema.js +74 -0
- package/dist/schema/json-schema.js.map +1 -0
- package/dist/schema/schema.d.ts +43 -0
- package/dist/schema/schema.d.ts.map +1 -0
- package/dist/schema/schema.js +102 -0
- package/dist/schema/schema.js.map +1 -0
- package/dist/schema/types.d.ts +201 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +2 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/search.d.ts +215 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +449 -0
- package/dist/search.js.map +1 -0
- package/dist/settings.d.ts +30 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +74 -0
- package/dist/settings.js.map +1 -0
- package/dist/store.d.ts +284 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +660 -0
- package/dist/store.js.map +1 -0
- package/dist/studio-door.d.ts +187 -0
- package/dist/studio-door.d.ts.map +1 -0
- package/dist/studio-door.js +20 -0
- package/dist/studio-door.js.map +1 -0
- package/dist/sync/conflict.d.ts +37 -0
- package/dist/sync/conflict.d.ts.map +1 -0
- package/dist/sync/conflict.js +56 -0
- package/dist/sync/conflict.js.map +1 -0
- package/dist/sync/engine.d.ts +118 -0
- package/dist/sync/engine.d.ts.map +1 -0
- package/dist/sync/engine.js +341 -0
- package/dist/sync/engine.js.map +1 -0
- package/dist/sync/google-calendar.d.ts +54 -0
- package/dist/sync/google-calendar.d.ts.map +1 -0
- package/dist/sync/google-calendar.js +167 -0
- package/dist/sync/google-calendar.js.map +1 -0
- package/dist/sync/types.d.ts +125 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +6 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/temporal/effectivity.d.ts +75 -0
- package/dist/temporal/effectivity.d.ts.map +1 -0
- package/dist/temporal/effectivity.js +89 -0
- package/dist/temporal/effectivity.js.map +1 -0
- package/dist/testing.d.ts +63 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +194 -0
- package/dist/testing.js.map +1 -0
- package/dist/theme/contrast.d.ts +47 -0
- package/dist/theme/contrast.d.ts.map +1 -0
- package/dist/theme/contrast.js +172 -0
- package/dist/theme/contrast.js.map +1 -0
- package/dist/theme/derive.d.ts +81 -0
- package/dist/theme/derive.d.ts.map +1 -0
- package/dist/theme/derive.js +171 -0
- package/dist/theme/derive.js.map +1 -0
- package/dist/theme/kit.d.ts +106 -0
- package/dist/theme/kit.d.ts.map +1 -0
- package/dist/theme/kit.js +84 -0
- package/dist/theme/kit.js.map +1 -0
- package/dist/theme/palettes.d.ts +32 -0
- package/dist/theme/palettes.d.ts.map +1 -0
- package/dist/theme/palettes.js +96 -0
- package/dist/theme/palettes.js.map +1 -0
- package/dist/theme/types.d.ts +131 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/theme/types.js +23 -0
- package/dist/theme/types.js.map +1 -0
- package/dist/views/types.d.ts +89 -0
- package/dist/views/types.d.ts.map +1 -0
- package/dist/views/types.js +88 -0
- package/dist/views/types.js.map +1 -0
- package/dist/watched.d.ts +33 -0
- package/dist/watched.d.ts.map +1 -0
- package/dist/watched.js +50 -0
- package/dist/watched.js.map +1 -0
- package/dist/who.d.ts +26 -0
- package/dist/who.d.ts.map +1 -0
- package/dist/who.js +20 -0
- package/dist/who.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { EMPTY_SYNC_STATE, systemAuthor, } from "./types.js";
|
|
2
|
+
/** Reads a node's mapped values, in OUR field names. */
|
|
3
|
+
function mappedValues(node, mapping) {
|
|
4
|
+
const values = {};
|
|
5
|
+
for (const ours of Object.keys(mapping.fields))
|
|
6
|
+
values[ours] = node[ours];
|
|
7
|
+
return values;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Turns a remote record into our field names AND our representation.
|
|
11
|
+
*
|
|
12
|
+
* Both halves matter. A mapping that only renames ships a field full of
|
|
13
|
+
* nonsense the first time the two systems disagree about what a time is.
|
|
14
|
+
*/
|
|
15
|
+
function ourFields(remote, mapping) {
|
|
16
|
+
const values = {};
|
|
17
|
+
for (const [ours, theirs] of Object.entries(mapping.fields)) {
|
|
18
|
+
if (mapping.ours?.includes(ours))
|
|
19
|
+
continue;
|
|
20
|
+
if (!(theirs in remote))
|
|
21
|
+
continue;
|
|
22
|
+
const decode = mapping.decode?.[ours];
|
|
23
|
+
values[ours] = decode ? decode(remote[theirs]) : remote[theirs];
|
|
24
|
+
}
|
|
25
|
+
return values;
|
|
26
|
+
}
|
|
27
|
+
function remoteFields(values, mapping, node = values) {
|
|
28
|
+
const out = {};
|
|
29
|
+
for (const [ours, theirs] of Object.entries(mapping.fields)) {
|
|
30
|
+
if (!(ours in values))
|
|
31
|
+
continue;
|
|
32
|
+
const encode = mapping.encode?.[ours];
|
|
33
|
+
out[theirs] = encode ? encode(values[ours], node) : values[ours];
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
38
|
+
/**
|
|
39
|
+
* Two-way sync, on top of the op log rather than beside it.
|
|
40
|
+
*
|
|
41
|
+
* The log gives this a foundation most sync layers lack. Every op already
|
|
42
|
+
* carries who did it and what it wrote, so an inbound change arrives as an
|
|
43
|
+
* ordinary op authored by the system, appears in the activity list beside a
|
|
44
|
+
* person's edits, and is undone like anything else. And because we remember
|
|
45
|
+
* the version the remote produced for each of OUR writes, an echo of our own
|
|
46
|
+
* write is distinguishable from somebody else's change — which is the loop
|
|
47
|
+
* that breaks naive two-way sync.
|
|
48
|
+
*
|
|
49
|
+
* Nothing here resolves a conflict. A field both sides changed since they last
|
|
50
|
+
* agreed is reported, and it is the app's business — the same instinct that
|
|
51
|
+
* makes an invariant name its repairs rather than silently applying one.
|
|
52
|
+
*/
|
|
53
|
+
export class SyncEngine {
|
|
54
|
+
state;
|
|
55
|
+
options;
|
|
56
|
+
constructor(options, state = EMPTY_SYNC_STATE) {
|
|
57
|
+
this.options = options;
|
|
58
|
+
this.state = state;
|
|
59
|
+
}
|
|
60
|
+
get snapshot() {
|
|
61
|
+
return this.state;
|
|
62
|
+
}
|
|
63
|
+
mappingFor(kindOrResource) {
|
|
64
|
+
return this.options.mapping.resources.find((resource) => resource.kind === kindOrResource || resource.resource === kindOrResource);
|
|
65
|
+
}
|
|
66
|
+
/** Every node that participates in the sync, by our id. */
|
|
67
|
+
participants() {
|
|
68
|
+
const graph = this.options.store.graph;
|
|
69
|
+
const found = new Map();
|
|
70
|
+
for (const node of graph.allNodes()) {
|
|
71
|
+
const mapping = this.mappingFor(node["kind"]);
|
|
72
|
+
if (!mapping || mapping.kind !== node["kind"])
|
|
73
|
+
continue;
|
|
74
|
+
if (mapping.match && !mapping.match(node))
|
|
75
|
+
continue;
|
|
76
|
+
found.set(node["id"], { node, mapping });
|
|
77
|
+
}
|
|
78
|
+
return found;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The local changes the remote has not seen.
|
|
82
|
+
*
|
|
83
|
+
* Derived by comparing what a node holds NOW against what we last agreed it
|
|
84
|
+
* held — not by watching for edits. A change nobody was listening for still
|
|
85
|
+
* gets sent, which matters because the alternative is a subscription that
|
|
86
|
+
* has to be correct forever.
|
|
87
|
+
*/
|
|
88
|
+
pending() {
|
|
89
|
+
const writes = [];
|
|
90
|
+
const seen = new Set();
|
|
91
|
+
for (const [localId, { node, mapping }] of this.participants()) {
|
|
92
|
+
seen.add(localId);
|
|
93
|
+
const link = this.state.links[localId];
|
|
94
|
+
const values = mappedValues(node, mapping);
|
|
95
|
+
if (!link) {
|
|
96
|
+
writes.push({
|
|
97
|
+
resource: mapping.resource,
|
|
98
|
+
localId,
|
|
99
|
+
fields: remoteFields(values, mapping, node),
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const changed = Object.keys(values).filter((field) => !same(values[field], link.fields[field]));
|
|
104
|
+
if (changed.length === 0)
|
|
105
|
+
continue;
|
|
106
|
+
writes.push({
|
|
107
|
+
resource: mapping.resource,
|
|
108
|
+
id: link.id,
|
|
109
|
+
localId,
|
|
110
|
+
fields: remoteFields(values, mapping, node),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Something we had linked and no longer have is a deletion, and saying so
|
|
114
|
+
// is better than leaving an orphan on the remote forever.
|
|
115
|
+
for (const [localId, link] of Object.entries(this.state.links)) {
|
|
116
|
+
if (seen.has(localId))
|
|
117
|
+
continue;
|
|
118
|
+
writes.push({ resource: link.resource, id: link.id, localId, deleted: true });
|
|
119
|
+
}
|
|
120
|
+
return writes;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* One pass: take what changed there, then send what changed here.
|
|
124
|
+
*
|
|
125
|
+
* Inbound first on purpose. Pushing first would send a value we are about to
|
|
126
|
+
* be told is stale, and then the conflict we report would be one we caused.
|
|
127
|
+
*/
|
|
128
|
+
async run() {
|
|
129
|
+
const applied = [];
|
|
130
|
+
const echoes = [];
|
|
131
|
+
const conflicts = [];
|
|
132
|
+
const pushed = [];
|
|
133
|
+
const refused = [];
|
|
134
|
+
let offline = false;
|
|
135
|
+
let links = { ...this.state.links };
|
|
136
|
+
let cursor = this.state.cursor;
|
|
137
|
+
// --------------------------------------------------------------- inbound
|
|
138
|
+
try {
|
|
139
|
+
const result = await this.options.remote.pull(cursor);
|
|
140
|
+
cursor = result.cursor;
|
|
141
|
+
const byRemoteId = new Map(Object.entries(links).map(([localId, link]) => [`${link.resource}:${link.id}`, localId]));
|
|
142
|
+
for (const change of result.changes) {
|
|
143
|
+
const mapping = this.mappingFor(change.resource);
|
|
144
|
+
if (!mapping)
|
|
145
|
+
continue;
|
|
146
|
+
const localId = byRemoteId.get(`${change.resource}:${change.id}`);
|
|
147
|
+
/*
|
|
148
|
+
* OUR OWN WRITE, COMING BACK.
|
|
149
|
+
*
|
|
150
|
+
* The remote told us what version our push produced; a change carrying
|
|
151
|
+
* that version is that push arriving as news. Applying it would be
|
|
152
|
+
* harmless once and a loop when two systems do it to each other.
|
|
153
|
+
*/
|
|
154
|
+
if (localId && links[localId].version === change.version) {
|
|
155
|
+
echoes.push(localId);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!localId) {
|
|
159
|
+
const created = this.options.createInbound?.({
|
|
160
|
+
resource: change.resource,
|
|
161
|
+
id: change.id,
|
|
162
|
+
fields: change.fields ?? {},
|
|
163
|
+
});
|
|
164
|
+
if (!created)
|
|
165
|
+
continue;
|
|
166
|
+
const result2 = this.options.store.apply(created, {
|
|
167
|
+
author: systemAuthor(this.options.mapping.system),
|
|
168
|
+
intent: `${this.options.mapping.system}: new ${change.resource}`,
|
|
169
|
+
});
|
|
170
|
+
const madeId = String(created.args["id"] ?? result2.writes[0] ?? "");
|
|
171
|
+
if (madeId) {
|
|
172
|
+
applied.push(madeId);
|
|
173
|
+
links[madeId] = {
|
|
174
|
+
resource: change.resource,
|
|
175
|
+
id: change.id,
|
|
176
|
+
version: change.version,
|
|
177
|
+
fields: ourFields(change.fields ?? {}, mapping),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const link = links[localId];
|
|
183
|
+
const node = this.options.store.graph.getNode(localId);
|
|
184
|
+
if (!node)
|
|
185
|
+
continue;
|
|
186
|
+
const theirs = ourFields(change.fields ?? {}, mapping);
|
|
187
|
+
const ours = mappedValues(node, mapping);
|
|
188
|
+
/*
|
|
189
|
+
* A GENUINE CONFLICT, reported rather than resolved.
|
|
190
|
+
*
|
|
191
|
+
* Both sides changed the same field since the version we last agreed
|
|
192
|
+
* on. Picking a winner here would be the sync layer making a domain
|
|
193
|
+
* decision on nobody's behalf, quietly, in a place nobody looks.
|
|
194
|
+
*/
|
|
195
|
+
const contested = Object.keys(theirs).filter((field) => !same(theirs[field], link.fields[field]) && !same(ours[field], link.fields[field]));
|
|
196
|
+
const settled = Object.fromEntries(Object.entries(theirs).filter(([field]) => !contested.includes(field)));
|
|
197
|
+
for (const field of contested) {
|
|
198
|
+
conflicts.push({
|
|
199
|
+
localId,
|
|
200
|
+
field,
|
|
201
|
+
ours: ours[field],
|
|
202
|
+
theirs: theirs[field],
|
|
203
|
+
base: link.fields[field],
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (Object.keys(settled).length > 0) {
|
|
207
|
+
this.options.store.apply(this.options.applyInbound({ localId, kind: mapping.kind, fields: settled }), {
|
|
208
|
+
author: systemAuthor(this.options.mapping.system),
|
|
209
|
+
intent: `${this.options.mapping.system}: ${Object.keys(settled).join(", ")}`,
|
|
210
|
+
});
|
|
211
|
+
applied.push(localId);
|
|
212
|
+
}
|
|
213
|
+
links[localId] = {
|
|
214
|
+
...link,
|
|
215
|
+
version: change.version,
|
|
216
|
+
// The uncontested fields are agreed now; the contested ones are not,
|
|
217
|
+
// so they keep their old base until somebody resolves them.
|
|
218
|
+
fields: { ...link.fields, ...settled },
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
/*
|
|
224
|
+
* OFFLINE degrades to local-only.
|
|
225
|
+
*
|
|
226
|
+
* Not an error: a household week that stops working on a train is worse
|
|
227
|
+
* than one that syncs later. The cursor is untouched, so the next run
|
|
228
|
+
* asks for the same window again.
|
|
229
|
+
*/
|
|
230
|
+
offline = true;
|
|
231
|
+
}
|
|
232
|
+
// -------------------------------------------------------------- outbound
|
|
233
|
+
const outbox = [...this.state.outbox, ...this.pending()];
|
|
234
|
+
// The last write for a node wins; sending three versions of the same edit
|
|
235
|
+
// is how a remote's rate limit is discovered.
|
|
236
|
+
const deduped = [...new Map(outbox.map((write) => [write.localId, write])).values()];
|
|
237
|
+
let queued = deduped;
|
|
238
|
+
if (!offline && deduped.length > 0) {
|
|
239
|
+
try {
|
|
240
|
+
const acks = await this.options.remote.push(deduped);
|
|
241
|
+
queued = [];
|
|
242
|
+
for (const ack of acks) {
|
|
243
|
+
if (ack.error) {
|
|
244
|
+
refused.push({ localId: ack.localId, error: ack.error });
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
pushed.push(ack.localId);
|
|
248
|
+
links = this.linkAfterPush(links, ack, deduped);
|
|
249
|
+
}
|
|
250
|
+
// Anything the remote did not acknowledge at all stays queued rather
|
|
251
|
+
// than being assumed delivered.
|
|
252
|
+
const acked = new Set(acks.map((ack) => ack.localId));
|
|
253
|
+
queued = deduped.filter((write) => !acked.has(write.localId));
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
offline = true;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
this.state = {
|
|
260
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
261
|
+
links,
|
|
262
|
+
outbox: queued,
|
|
263
|
+
};
|
|
264
|
+
return {
|
|
265
|
+
system: this.options.mapping.system,
|
|
266
|
+
applied,
|
|
267
|
+
echoes,
|
|
268
|
+
conflicts,
|
|
269
|
+
pushed,
|
|
270
|
+
queued: queued.map((write) => write.localId),
|
|
271
|
+
refused,
|
|
272
|
+
offline,
|
|
273
|
+
state: this.state,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/** Records what the remote made of one of our writes. */
|
|
277
|
+
linkAfterPush(links, ack, writes) {
|
|
278
|
+
const write = writes.find((candidate) => candidate.localId === ack.localId);
|
|
279
|
+
if (!write)
|
|
280
|
+
return links;
|
|
281
|
+
if (write.deleted) {
|
|
282
|
+
const { [ack.localId]: _gone, ...rest } = links;
|
|
283
|
+
return rest;
|
|
284
|
+
}
|
|
285
|
+
const mapping = this.mappingFor(write.resource);
|
|
286
|
+
if (!mapping)
|
|
287
|
+
return links;
|
|
288
|
+
return {
|
|
289
|
+
...links,
|
|
290
|
+
[ack.localId]: {
|
|
291
|
+
resource: write.resource,
|
|
292
|
+
id: ack.id,
|
|
293
|
+
// The version OUR write produced. This is the whole echo mechanism.
|
|
294
|
+
version: ack.version,
|
|
295
|
+
// Stored in OUR representation, because that is what the next run
|
|
296
|
+
// compares against — comparing a decoded remote value with an encoded
|
|
297
|
+
// local one reports a change on every single pass.
|
|
298
|
+
fields: ourFields(write.fields ?? {}, mapping),
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Accepts one side of a conflict, and agrees the base so it stops being one.
|
|
304
|
+
*
|
|
305
|
+
* `theirs` writes their value through the ordinary inbound mutation, so it
|
|
306
|
+
* lands in the log like any other change. `ours` changes nothing locally and
|
|
307
|
+
* simply re-bases, which makes the next run push our value.
|
|
308
|
+
*/
|
|
309
|
+
resolve(conflict, take) {
|
|
310
|
+
const link = this.state.links[conflict.localId];
|
|
311
|
+
if (!link)
|
|
312
|
+
return;
|
|
313
|
+
const mapping = this.mappingFor(link.resource);
|
|
314
|
+
if (!mapping)
|
|
315
|
+
return;
|
|
316
|
+
if (take === "theirs") {
|
|
317
|
+
this.options.store.apply(this.options.applyInbound({
|
|
318
|
+
localId: conflict.localId,
|
|
319
|
+
kind: mapping.kind,
|
|
320
|
+
fields: { [conflict.field]: conflict.theirs },
|
|
321
|
+
}), {
|
|
322
|
+
author: systemAuthor(this.options.mapping.system),
|
|
323
|
+
intent: `${this.options.mapping.system}: took their "${conflict.field}"`,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
this.state = {
|
|
327
|
+
...this.state,
|
|
328
|
+
links: {
|
|
329
|
+
...this.state.links,
|
|
330
|
+
[conflict.localId]: {
|
|
331
|
+
...link,
|
|
332
|
+
fields: {
|
|
333
|
+
...link.fields,
|
|
334
|
+
[conflict.field]: take === "theirs" ? conflict.theirs : conflict.base,
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/sync/engine.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,gBAAgB,EAChB,YAAY,GAQb,MAAM,YAAY,CAAC;AAqCpB,wDAAwD;AACxD,SAAS,YAAY,CACnB,IAA6B,EAC7B,OAAwB;IAExB,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1E,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,SAAS,SAAS,CAChB,MAAyC,EACzC,OAAwB;IAExB,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5D,IAAI,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;YAAE,SAAS;QAC3C,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC;YAAE,SAAS;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,YAAY,CACnB,MAA+B,EAC/B,OAAwB,EACxB,OAAgC,MAAM;IAEtC,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5D,IAAI,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC;YAAE,SAAS;QAChC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;QACtC,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,IAAI,GAAG,CAAC,CAAU,EAAE,CAAU,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAyBjF;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,UAAU;IACb,KAAK,CAAY;IACR,OAAO,CAAiB;IAEzC,YAAY,OAAuB,EAAE,QAAmB,gBAAgB;QACtE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAEO,UAAU,CAAC,cAAsB;QACvC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CACxC,CAAC,QAAQ,EAAE,EAAE,CACX,QAAQ,CAAC,IAAI,KAAK,cAAc,IAAI,QAAQ,CAAC,QAAQ,KAAK,cAAc,CAC3E,CAAC;IACJ,CAAC;IAED,2DAA2D;IACnD,YAAY;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAqC,CAAC;QACvE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAuE,CAAC;QAC7F,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,QAAQ,EAA+B,EAAE,CAAC;YACjE,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAW,CAAC,CAAC;YACxD,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC;gBAAE,SAAS;YACxD,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YACpD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAW,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;;OAOG;IACH,OAAO;QACL,MAAM,MAAM,GAAkB,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,IAAI,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YAC/D,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAClB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACvC,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC3C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,CAAC;oBACV,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,OAAO;oBACP,MAAM,EAAE,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;iBAC5C,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAChG,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YACnC,MAAM,CAAC,IAAI,CAAC;gBACV,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,EAAE,EAAE,IAAI,CAAC,EAAE;gBACX,OAAO;gBACP,MAAM,EAAE,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;aAC5C,CAAC,CAAC;QACL,CAAC;QACD,0EAA0E;QAC1E,0DAA0D;QAC1D,KAAK,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/D,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,SAAS;YAChC,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAChF,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,GAAG;QACP,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAmB,EAAE,CAAC;QACrC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAyC,EAAE,CAAC;QACzD,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,IAAI,KAAK,GAA+B,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QAChE,IAAI,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAE/B,0EAA0E;QAC1E,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACtD,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YACvB,MAAM,UAAU,GAAG,IAAI,GAAG,CACxB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CACzF,CAAC;YAEF,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACjD,IAAI,CAAC,OAAO;oBAAE,SAAS;gBACvB,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;gBAElE;;;;;;mBAMG;gBACH,IAAI,OAAO,IAAI,KAAK,CAAC,OAAO,CAAE,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC1D,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBACrB,SAAS;gBACX,CAAC;gBAED,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;wBAC3C,QAAQ,EAAE,MAAM,CAAC,QAAQ;wBACzB,EAAE,EAAE,MAAM,CAAC,EAAE;wBACb,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,EAAE;qBAC5B,CAAC,CAAC;oBACH,IAAI,CAAC,OAAO;wBAAE,SAAS;oBACvB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE;wBAChD,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;wBACjD,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,SAAS,MAAM,CAAC,QAAQ,EAAE;qBACjE,CAAC,CAAC;oBACH,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;oBACrE,IAAI,MAAM,EAAE,CAAC;wBACX,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;wBACrB,KAAK,CAAC,MAAM,CAAC,GAAG;4BACd,QAAQ,EAAE,MAAM,CAAC,QAAQ;4BACzB,EAAE,EAAE,MAAM,CAAC,EAAE;4BACb,OAAO,EAAE,MAAM,CAAC,OAAO;4BACvB,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,OAAO,CAAC;yBAChD,CAAC;oBACJ,CAAC;oBACD,SAAS;gBACX,CAAC;gBAED,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAE,CAAC;gBAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAExC,CAAC;gBACd,IAAI,CAAC,IAAI;oBAAE,SAAS;gBACpB,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;gBACvD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBAEzC;;;;;;mBAMG;gBACH,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAC1C,CAAC,KAAK,EAAE,EAAE,CACR,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CACrF,CAAC;gBACF,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAChC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CACvE,CAAC;gBACF,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;oBAC9B,SAAS,CAAC,IAAI,CAAC;wBACb,OAAO;wBACP,KAAK;wBACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;wBACjB,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC;wBACrB,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;qBACzB,CAAC,CAAC;gBACL,CAAC;gBAED,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACpC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CACtB,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAC3E;wBACE,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;wBACjD,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;qBAC7E,CACF,CAAC;oBACF,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACxB,CAAC;gBACD,KAAK,CAAC,OAAO,CAAC,GAAG;oBACf,GAAG,IAAI;oBACP,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,qEAAqE;oBACrE,4DAA4D;oBAC5D,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,OAAO,EAAE;iBACvC,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP;;;;;;eAMG;YACH,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QAED,0EAA0E;QAC1E,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACzD,0EAA0E;QAC1E,8CAA8C;QAC9C,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACrF,IAAI,MAAM,GAAkB,OAAO,CAAC;QAEpC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACrD,MAAM,GAAG,EAAE,CAAC;gBACZ,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;oBACvB,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;wBACd,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;wBACzD,SAAS;oBACX,CAAC;oBACD,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBACzB,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;gBAClD,CAAC;gBACD,qEAAqE;gBACrE,gCAAgC;gBAChC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;gBACtD,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YAChE,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;QACH,CAAC;QAED,IAAI,CAAC,KAAK,GAAG;YACX,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;YAC3C,KAAK;YACL,MAAM,EAAE,MAAM;SACf,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM;YACnC,OAAO;YACP,MAAM;YACN,SAAS;YACT,MAAM;YACN,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC;YAC5C,OAAO;YACP,OAAO;YACP,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC;IACJ,CAAC;IAED,yDAAyD;IACjD,aAAa,CACnB,KAAiC,EACjC,GAAc,EACd,MAA8B;QAE9B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5E,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;YAChD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,OAAO;YACL,GAAG,KAAK;YACR,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE;gBACb,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,oEAAoE;gBACpE,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,kEAAkE;gBAClE,sEAAsE;gBACtE,mDAAmD;gBACnD,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,OAAO,CAAC;aAC/C;SACF,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,QAAsB,EAAE,IAAuB;QACrD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/C,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CACtB,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC;gBACxB,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,EAAE;aAC9C,CAAC,EACF;gBACE,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;gBACjD,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,iBAAiB,QAAQ,CAAC,KAAK,GAAG;aACzE,CACF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,KAAK,GAAG;YACX,GAAG,IAAI,CAAC,KAAK;YACb,KAAK,EAAE;gBACL,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK;gBACnB,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE;oBAClB,GAAG,IAAI;oBACP,MAAM,EAAE;wBACN,GAAG,IAAI,CAAC,MAAM;wBACd,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI;qBACtE;iBACF;aACF;SACF,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { RemoteSystem, SyncMapping } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Google Calendar, as a transport.
|
|
4
|
+
*
|
|
5
|
+
* The household example's blocks and duties are literally calendar events, which is why
|
|
6
|
+
* this is the honest first test of the sync layer rather than a hypothetical
|
|
7
|
+
* one — a sync design that only ever ran against a fixture has not met the
|
|
8
|
+
* things that actually go wrong.
|
|
9
|
+
*
|
|
10
|
+
* Everything Google-specific is here. The engine knows about resources,
|
|
11
|
+
* versions and acks; it has never heard of `etag`, `syncToken` or RFC 3339.
|
|
12
|
+
*/
|
|
13
|
+
/** The mapping an app declares. Fields, not code. */
|
|
14
|
+
export declare function googleCalendarMapping(options: {
|
|
15
|
+
/** Which of your kinds are events, and which of your fields are which. */
|
|
16
|
+
readonly events: readonly {
|
|
17
|
+
readonly kind: string;
|
|
18
|
+
readonly summary: string;
|
|
19
|
+
readonly start: string;
|
|
20
|
+
readonly end: string;
|
|
21
|
+
readonly location?: string;
|
|
22
|
+
readonly description?: string;
|
|
23
|
+
readonly match?: (node: Record<string, unknown>) => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Where your representation is not Google's.
|
|
26
|
+
*
|
|
27
|
+
* Google wants RFC 3339. If your `start` is minutes since midnight, or a
|
|
28
|
+
* day index, or anything else that is not that, say how to get from one to
|
|
29
|
+
* the other — a rename cannot, and a mapping that only renames ships a
|
|
30
|
+
* field full of nonsense the first time it runs.
|
|
31
|
+
*/
|
|
32
|
+
readonly encodeTime?: (value: unknown, node: Record<string, unknown>) => unknown;
|
|
33
|
+
readonly decodeTime?: (value: unknown) => unknown;
|
|
34
|
+
}[];
|
|
35
|
+
}): SyncMapping;
|
|
36
|
+
/** The bit of `fetch` this needs, so a test can supply a recording. */
|
|
37
|
+
export type Fetcher = (url: string, init?: {
|
|
38
|
+
method?: string;
|
|
39
|
+
headers?: Record<string, string>;
|
|
40
|
+
body?: string;
|
|
41
|
+
}) => Promise<{
|
|
42
|
+
ok: boolean;
|
|
43
|
+
status: number;
|
|
44
|
+
json(): Promise<unknown>;
|
|
45
|
+
}>;
|
|
46
|
+
export interface GoogleCalendarOptions {
|
|
47
|
+
readonly calendarId: string;
|
|
48
|
+
/** Called before every request, so a refreshed token is picked up. */
|
|
49
|
+
token(): Promise<string> | string;
|
|
50
|
+
readonly fetch?: Fetcher;
|
|
51
|
+
readonly baseUrl?: string;
|
|
52
|
+
}
|
|
53
|
+
export declare function googleCalendar(options: GoogleCalendarOptions): RemoteSystem;
|
|
54
|
+
//# sourceMappingURL=google-calendar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-calendar.d.ts","sourceRoot":"","sources":["../../src/sync/google-calendar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA2B,YAAY,EAAe,WAAW,EAAE,MAAM,YAAY,CAAC;AAElG;;;;;;;;;;GAUG;AAEH,qDAAqD;AACrD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAC7C,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,SAAS;QACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;QAC5D;;;;;;;WAOG;QACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;QACjF,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;KACnD,EAAE,CAAC;CACL,GAAG,WAAW,CAsBd;AAED,uEAAuE;AACvE,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KACxE,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC;AAExE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,sEAAsE;IACtE,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AA0BD,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,YAAY,CAgG3E"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Calendar, as a transport.
|
|
3
|
+
*
|
|
4
|
+
* The household example's blocks and duties are literally calendar events, which is why
|
|
5
|
+
* this is the honest first test of the sync layer rather than a hypothetical
|
|
6
|
+
* one — a sync design that only ever ran against a fixture has not met the
|
|
7
|
+
* things that actually go wrong.
|
|
8
|
+
*
|
|
9
|
+
* Everything Google-specific is here. The engine knows about resources,
|
|
10
|
+
* versions and acks; it has never heard of `etag`, `syncToken` or RFC 3339.
|
|
11
|
+
*/
|
|
12
|
+
/** The mapping an app declares. Fields, not code. */
|
|
13
|
+
export function googleCalendarMapping(options) {
|
|
14
|
+
return {
|
|
15
|
+
system: "google-calendar",
|
|
16
|
+
resources: options.events.map((event) => ({
|
|
17
|
+
kind: event.kind,
|
|
18
|
+
resource: "events",
|
|
19
|
+
fields: {
|
|
20
|
+
[event.summary]: "summary",
|
|
21
|
+
[event.start]: "start.dateTime",
|
|
22
|
+
[event.end]: "end.dateTime",
|
|
23
|
+
...(event.location ? { [event.location]: "location" } : {}),
|
|
24
|
+
...(event.description ? { [event.description]: "description" } : {}),
|
|
25
|
+
},
|
|
26
|
+
...(event.match ? { match: event.match } : {}),
|
|
27
|
+
...(event.encodeTime
|
|
28
|
+
? { encode: { [event.start]: event.encodeTime, [event.end]: event.encodeTime } }
|
|
29
|
+
: {}),
|
|
30
|
+
...(event.decodeTime
|
|
31
|
+
? { decode: { [event.start]: event.decodeTime, [event.end]: event.decodeTime } }
|
|
32
|
+
: {}),
|
|
33
|
+
})),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const BASE = "https://www.googleapis.com/calendar/v3";
|
|
37
|
+
/** Reads a dotted path out of a nested response — `start.dateTime`. */
|
|
38
|
+
function at(record, path) {
|
|
39
|
+
return path
|
|
40
|
+
.split(".")
|
|
41
|
+
.reduce((value, key) => value && typeof value === "object" ? value[key] : undefined, record);
|
|
42
|
+
}
|
|
43
|
+
/** Writes a dotted path into a nested request body. */
|
|
44
|
+
function put(into, path, value) {
|
|
45
|
+
const keys = path.split(".");
|
|
46
|
+
let cursor = into;
|
|
47
|
+
for (const key of keys.slice(0, -1)) {
|
|
48
|
+
cursor[key] ??= {};
|
|
49
|
+
cursor = cursor[key];
|
|
50
|
+
}
|
|
51
|
+
cursor[keys.at(-1)] = value;
|
|
52
|
+
}
|
|
53
|
+
export function googleCalendar(options) {
|
|
54
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
55
|
+
const base = options.baseUrl ?? BASE;
|
|
56
|
+
const events = `${base}/calendars/${encodeURIComponent(options.calendarId)}/events`;
|
|
57
|
+
const call = async (url, init) => {
|
|
58
|
+
const token = await options.token();
|
|
59
|
+
const response = await doFetch(url, {
|
|
60
|
+
...init,
|
|
61
|
+
headers: {
|
|
62
|
+
authorization: `Bearer ${token}`,
|
|
63
|
+
"content-type": "application/json",
|
|
64
|
+
...(init?.headers ?? {}),
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
/*
|
|
68
|
+
* A 410 means the sync token expired and Google wants a full resync. It
|
|
69
|
+
* is the one error here that is not an error: throwing would look like
|
|
70
|
+
* being offline, and being offline is handled by keeping the cursor —
|
|
71
|
+
* which is exactly the wrong response to "your cursor is no good".
|
|
72
|
+
*/
|
|
73
|
+
if (response.status === 410)
|
|
74
|
+
return { expired: true };
|
|
75
|
+
if (!response.ok)
|
|
76
|
+
throw new Error(`google-calendar: HTTP ${response.status}`);
|
|
77
|
+
return { expired: false, body: (await response.json()) };
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
name: "google-calendar",
|
|
81
|
+
async pull(cursor) {
|
|
82
|
+
const url = new URL(events);
|
|
83
|
+
if (cursor)
|
|
84
|
+
url.searchParams.set("syncToken", cursor);
|
|
85
|
+
// Without this a deleted event simply stops appearing, and an event
|
|
86
|
+
// that stops appearing is indistinguishable from one we never had.
|
|
87
|
+
else
|
|
88
|
+
url.searchParams.set("showDeleted", "true");
|
|
89
|
+
let result = await call(url.toString());
|
|
90
|
+
if (result.expired) {
|
|
91
|
+
// Start again from nothing rather than silently missing a window.
|
|
92
|
+
result = await call(`${events}?showDeleted=true`);
|
|
93
|
+
if (result.expired)
|
|
94
|
+
throw new Error("google-calendar: sync token rejected twice");
|
|
95
|
+
}
|
|
96
|
+
const body = result.body;
|
|
97
|
+
const items = body["items"] ?? [];
|
|
98
|
+
const changes = items.map((item) => ({
|
|
99
|
+
resource: "events",
|
|
100
|
+
id: String(item["id"]),
|
|
101
|
+
// The etag is Google's own version of the record, and the reason an
|
|
102
|
+
// echo of our own write is recognisable at all.
|
|
103
|
+
version: String(item["etag"] ?? ""),
|
|
104
|
+
...(item["status"] === "cancelled"
|
|
105
|
+
? { deleted: true }
|
|
106
|
+
: { fields: flatten(item) }),
|
|
107
|
+
}));
|
|
108
|
+
return { changes, cursor: String(body["nextSyncToken"] ?? cursor ?? "") };
|
|
109
|
+
},
|
|
110
|
+
async push(writes) {
|
|
111
|
+
const acks = [];
|
|
112
|
+
for (const write of writes) {
|
|
113
|
+
try {
|
|
114
|
+
acks.push(await one(write));
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
// One refusal is not a failed run. The engine keeps the local state
|
|
118
|
+
// for this write and reports it; the rest still go.
|
|
119
|
+
acks.push({
|
|
120
|
+
localId: write.localId,
|
|
121
|
+
id: write.id ?? "",
|
|
122
|
+
version: "",
|
|
123
|
+
error: error instanceof Error ? error.message : String(error),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return acks;
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
async function one(write) {
|
|
131
|
+
if (write.deleted && write.id) {
|
|
132
|
+
await call(`${events}/${encodeURIComponent(write.id)}`, { method: "DELETE" });
|
|
133
|
+
return { localId: write.localId, id: write.id, version: "" };
|
|
134
|
+
}
|
|
135
|
+
const body = {};
|
|
136
|
+
for (const [path, value] of Object.entries(write.fields ?? {}))
|
|
137
|
+
put(body, path, value);
|
|
138
|
+
const result = await call(write.id ? `${events}/${encodeURIComponent(write.id)}` : events, {
|
|
139
|
+
method: write.id ? "PATCH" : "POST",
|
|
140
|
+
body: JSON.stringify(body),
|
|
141
|
+
});
|
|
142
|
+
if (result.expired)
|
|
143
|
+
throw new Error("google-calendar: unexpected 410 on write");
|
|
144
|
+
return {
|
|
145
|
+
localId: write.localId,
|
|
146
|
+
id: String(result.body["id"]),
|
|
147
|
+
version: String(result.body["etag"] ?? ""),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Google's nesting, flattened to the dotted paths the mapping uses.
|
|
153
|
+
*
|
|
154
|
+
* Only the paths a mapping can name — everything else in an event is Google's
|
|
155
|
+
* business, and carrying it around would mean an app could accidentally depend
|
|
156
|
+
* on a field it never declared.
|
|
157
|
+
*/
|
|
158
|
+
function flatten(item) {
|
|
159
|
+
const out = {};
|
|
160
|
+
for (const path of ["summary", "location", "description", "start.dateTime", "end.dateTime"]) {
|
|
161
|
+
const value = at(item, path);
|
|
162
|
+
if (value !== undefined)
|
|
163
|
+
out[path] = value;
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=google-calendar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-calendar.js","sourceRoot":"","sources":["../../src/sync/google-calendar.ts"],"names":[],"mappings":"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"}
|