clivly 0.12.0 → 0.14.1
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/README.md +10 -3
- package/{adapter-DhPBRNpi.d.mts → adapter-B-iBMhl0.d.mts} +13 -0
- package/{adapter-DTMeGGsl.d.cts → adapter-JpXSAnj-.d.cts} +13 -0
- package/core-adapter.d.cts +1 -1
- package/core-adapter.d.mts +1 -1
- package/core-sync-engine.cjs +3 -1
- package/core-sync-engine.d.cts +2 -2
- package/core-sync-engine.d.mts +2 -2
- package/core-sync-engine.mjs +2 -2
- package/core.cjs +3 -1
- package/core.d.cts +3 -3
- package/core.d.mts +3 -3
- package/core.mjs +2 -2
- package/drizzle.cjs +65 -0
- package/drizzle.d.cts +51 -1
- package/drizzle.d.mts +51 -1
- package/drizzle.mjs +64 -1
- package/index.cjs +102 -1
- package/index.mjs +102 -1
- package/internal-adapter.d.cts +1 -1
- package/internal-adapter.d.mts +1 -1
- package/internal-sync-engine.cjs +3 -1
- package/internal-sync-engine.d.cts +2 -2
- package/internal-sync-engine.d.mts +2 -2
- package/internal-sync-engine.mjs +2 -2
- package/internal.cjs +3 -1
- package/internal.d.cts +3 -3
- package/internal.d.mts +3 -3
- package/internal.mjs +2 -2
- package/{namespace-probe-CNQKSmxz.d.mts → namespace-probe-DykR0kgh.d.mts} +42 -0
- package/{namespace-probe-C5RLl74M.d.cts → namespace-probe-H7yWc0JV.d.cts} +42 -0
- package/package.json +1 -1
- package/{sdk-C-Pjxj2y.mjs → sdk-DLoVPlGB.mjs} +177 -6
- package/{sdk-BvGSxBQc.cjs → sdk-ZMy4C4VK.cjs} +194 -5
- package/sdk.cjs +4 -1
- package/sdk.d.cts +33 -1
- package/sdk.d.mts +33 -1
- package/sdk.mjs +2 -2
- package/{sync-engine-Dwm_KDUS.cjs → sync-engine-BGVDsrOQ.cjs} +131 -71
- package/{sync-engine-V3_UutGv.d.cts → sync-engine-CYsWOujz.d.cts} +63 -22
- package/{sync-engine--M9EaONC.d.mts → sync-engine-C_5TpvfX.d.mts} +63 -22
- package/{sync-engine-Df2MCs_n.mjs → sync-engine-DH058G3w.mjs} +120 -72
- package/vite.d.cts +1 -1
- package/vite.d.mts +1 -1
|
@@ -1,67 +1,103 @@
|
|
|
1
1
|
import { r as compileEntityViews } from "./view-compiler-C-6RydKK.mjs";
|
|
2
2
|
//#region ../core/src/sync-engine.ts
|
|
3
|
+
/** Actions that remove something — only safe when the source set is complete. */
|
|
4
|
+
function isRemovalAction(action) {
|
|
5
|
+
return action.kind === "archive" || action.kind === "endMembership";
|
|
6
|
+
}
|
|
7
|
+
/** Every distinct non-null company ref in a person's rows, in arrival order. */
|
|
8
|
+
function collectCompanyRefs(rows) {
|
|
9
|
+
const refs = [];
|
|
10
|
+
for (const row of rows) {
|
|
11
|
+
const ref = row.companyRef ?? null;
|
|
12
|
+
if (ref !== null && !refs.includes(ref)) refs.push(ref);
|
|
13
|
+
}
|
|
14
|
+
return refs;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The primary company for a person: the one they already have while it is
|
|
18
|
+
* still one of theirs — so the displayed organisation never flips between
|
|
19
|
+
* syncs — otherwise the lowest ref, which is deterministic for a new person.
|
|
20
|
+
*/
|
|
21
|
+
function pickPrimaryRef(refs, current) {
|
|
22
|
+
if (current !== null && refs.includes(current)) return current;
|
|
23
|
+
return [...refs].sort()[0] ?? null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The mirror row that represents a source identity. Normally there is exactly
|
|
27
|
+
* one; before contact consolidation has run there can be several (one per
|
|
28
|
+
* tenant), and the live, earliest-listed one stands for the person while the
|
|
29
|
+
* rest are left alone for the consolidation script to fold in.
|
|
30
|
+
*/
|
|
31
|
+
function canonicalMirror(rows) {
|
|
32
|
+
return rows.find((row) => !row.archivedAt) ?? rows[0];
|
|
33
|
+
}
|
|
3
34
|
/**
|
|
4
35
|
* Diff a view's rows against the current mirror population for one entity.
|
|
5
36
|
*
|
|
6
|
-
* - source
|
|
7
|
-
* - source
|
|
8
|
-
* - source
|
|
9
|
-
* - live mirror row absent from source
|
|
10
|
-
* - archived mirror row absent from source
|
|
37
|
+
* - source identity with no mirror row → create
|
|
38
|
+
* - source identity with a live mirror row → update (overwrite identity fields)
|
|
39
|
+
* - source identity with an archived mirror → restore (re-activate + update)
|
|
40
|
+
* - live mirror row absent from source → archive (soft delete, keep history)
|
|
41
|
+
* - archived mirror row absent from source → no-op
|
|
11
42
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
43
|
+
* Identity is always the source primary key. A `through` (join-table) view
|
|
44
|
+
* fans one person out into a row per tenant; those rows are ONE person, so they
|
|
45
|
+
* collapse into one action. With `memberships` on (contacts), that action also
|
|
46
|
+
* carries every tenant ref the person was seen with, and each tenant the mirror
|
|
47
|
+
* still has as active but the source no longer lists yields `endMembership` —
|
|
48
|
+
* a person leaving one tenant ends that membership and keeps the others.
|
|
18
49
|
*/
|
|
19
50
|
function reconcile(source, mirror, opts = {}) {
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const rows = [];
|
|
23
|
-
for (const row of source) {
|
|
24
|
-
const key = keyOf(row.sourceRef, row.companyRef ?? null);
|
|
25
|
-
if (!seen.has(key)) {
|
|
26
|
-
seen.add(key);
|
|
27
|
-
rows.push(row);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const byKey = new Map(mirror.map((m) => [keyOf(m.sourceRef, m.companyRef), m]));
|
|
31
|
-
const liveKeys = /* @__PURE__ */ new Set();
|
|
51
|
+
const rowsByRef = groupBySourceRef(source);
|
|
52
|
+
const mirrorByRef = groupBySourceRef(mirror);
|
|
32
53
|
const actions = [];
|
|
33
|
-
for (const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
});
|
|
51
|
-
else actions.push({
|
|
52
|
-
kind: "update",
|
|
53
|
-
id: existing.id,
|
|
54
|
-
sourceRef: row.sourceRef,
|
|
55
|
-
fields: row.fields,
|
|
56
|
-
companyRef
|
|
57
|
-
});
|
|
54
|
+
for (const [sourceRef, rows] of rowsByRef) {
|
|
55
|
+
const existing = canonicalMirror(mirrorByRef.get(sourceRef) ?? []);
|
|
56
|
+
actions.push(...personActions(sourceRef, rows, existing, opts));
|
|
57
|
+
}
|
|
58
|
+
for (const m of mirror) if (!(rowsByRef.has(m.sourceRef) || m.archivedAt)) actions.push({
|
|
59
|
+
kind: "archive",
|
|
60
|
+
id: m.id,
|
|
61
|
+
sourceRef: m.sourceRef
|
|
62
|
+
});
|
|
63
|
+
return actions;
|
|
64
|
+
}
|
|
65
|
+
function groupBySourceRef(items) {
|
|
66
|
+
const out = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const item of items) {
|
|
68
|
+
const list = out.get(item.sourceRef);
|
|
69
|
+
if (list) list.push(item);
|
|
70
|
+
else out.set(item.sourceRef, [item]);
|
|
58
71
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/** The create/update/restore for one present person, plus any ended tenants. */
|
|
75
|
+
function personActions(sourceRef, rows, existing, opts) {
|
|
76
|
+
const first = rows[0];
|
|
77
|
+
const refs = collectCompanyRefs(rows);
|
|
78
|
+
const companyRef = opts.memberships ? pickPrimaryRef(refs, existing?.companyRef ?? null) : first.companyRef ?? null;
|
|
79
|
+
const membershipSet = opts.memberships ? { companyRefs: refs } : {};
|
|
80
|
+
if (!existing) return [{
|
|
81
|
+
kind: "create",
|
|
82
|
+
sourceRef,
|
|
83
|
+
fields: first.fields,
|
|
84
|
+
companyRef,
|
|
85
|
+
...membershipSet
|
|
86
|
+
}];
|
|
87
|
+
const actions = [{
|
|
88
|
+
kind: existing.archivedAt ? "restore" : "update",
|
|
89
|
+
id: existing.id,
|
|
90
|
+
sourceRef,
|
|
91
|
+
fields: first.fields,
|
|
92
|
+
companyRef,
|
|
93
|
+
...membershipSet
|
|
94
|
+
}];
|
|
95
|
+
if (opts.memberships) {
|
|
96
|
+
for (const held of existing.companyRefs ?? []) if (!refs.includes(held)) actions.push({
|
|
97
|
+
kind: "endMembership",
|
|
98
|
+
id: existing.id,
|
|
99
|
+
sourceRef,
|
|
100
|
+
companyRef: held
|
|
65
101
|
});
|
|
66
102
|
}
|
|
67
103
|
return actions;
|
|
@@ -74,20 +110,40 @@ const CONCEPT_ORDER = {
|
|
|
74
110
|
function syncOrder(a, b) {
|
|
75
111
|
return (CONCEPT_ORDER[a.concept] ?? 99) - (CONCEPT_ORDER[b.concept] ?? 99);
|
|
76
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Resolve each action's host-side company refs — the primary one and, for
|
|
115
|
+
* contacts, every membership — to `crm_companies.id`s in one lookup. Shared by
|
|
116
|
+
* `runSync` and the cloud push receiver so both persist identical actions.
|
|
117
|
+
*/
|
|
77
118
|
async function resolveActions(store, actions) {
|
|
78
119
|
const refs = /* @__PURE__ */ new Set();
|
|
79
|
-
for (const action of actions)
|
|
120
|
+
for (const action of actions) {
|
|
121
|
+
if (isRemovalAction(action) || !("fields" in action)) continue;
|
|
122
|
+
if (action.companyRef) refs.add(action.companyRef);
|
|
123
|
+
for (const ref of action.companyRefs ?? []) refs.add(ref);
|
|
124
|
+
}
|
|
80
125
|
const resolved = refs.size ? await store.resolveCompanyRefs([...refs]) : /* @__PURE__ */ new Map();
|
|
81
126
|
return actions.map((action) => {
|
|
82
|
-
if (action.kind === "archive") return action;
|
|
127
|
+
if (action.kind === "archive" || action.kind === "endMembership") return action;
|
|
83
128
|
const companyId = action.companyRef ? resolved.get(action.companyRef) ?? null : null;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
129
|
+
const memberships = action.companyRefs?.map((companyRef) => ({
|
|
130
|
+
companyRef,
|
|
131
|
+
companyId: resolved.get(companyRef) ?? null
|
|
132
|
+
}));
|
|
133
|
+
const base = {
|
|
87
134
|
sourceRef: action.sourceRef,
|
|
88
135
|
fields: action.fields,
|
|
89
136
|
companyId,
|
|
90
|
-
companySourceRef: action.companyRef ?? null
|
|
137
|
+
companySourceRef: action.companyRef ?? null,
|
|
138
|
+
...memberships ? { memberships } : {}
|
|
139
|
+
};
|
|
140
|
+
return action.kind === "create" ? {
|
|
141
|
+
kind: "create",
|
|
142
|
+
...base
|
|
143
|
+
} : {
|
|
144
|
+
kind: action.kind,
|
|
145
|
+
id: action.id,
|
|
146
|
+
...base
|
|
91
147
|
};
|
|
92
148
|
});
|
|
93
149
|
}
|
|
@@ -96,19 +152,11 @@ function emptyTally() {
|
|
|
96
152
|
create: 0,
|
|
97
153
|
update: 0,
|
|
98
154
|
restore: 0,
|
|
99
|
-
archive: 0
|
|
155
|
+
archive: 0,
|
|
156
|
+
endMembership: 0
|
|
100
157
|
};
|
|
101
158
|
}
|
|
102
159
|
/**
|
|
103
|
-
* An entity whose company relationship is a `through` (join-table) join fans out
|
|
104
|
-
* into one source row per membership, so its mirror must be keyed compositely.
|
|
105
|
-
*/
|
|
106
|
-
function isCompositeEntity(config, entityKey) {
|
|
107
|
-
const entity = config.entities[entityKey];
|
|
108
|
-
if (!entity?.relationships) return false;
|
|
109
|
-
return Object.values(entity.relationships).some((rel) => "through" in rel.via);
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
160
|
* Run a full sync: compile the config's views, then reconcile + persist each,
|
|
113
161
|
* companies first. Returns per-entity and total action counts.
|
|
114
162
|
*/
|
|
@@ -129,7 +177,7 @@ async function runSync(config, store, options = {}) {
|
|
|
129
177
|
role: view.role,
|
|
130
178
|
objectTypeId
|
|
131
179
|
})]);
|
|
132
|
-
const actions = reconcile(source, mirror, {
|
|
180
|
+
const actions = reconcile(source, mirror, { memberships: view.concept === "contact" }).filter((a) => !(options.sample && isRemovalAction(a)));
|
|
133
181
|
const persistActions = await resolveActions(store, actions);
|
|
134
182
|
await store.persist({
|
|
135
183
|
concept: view.concept,
|
|
@@ -211,4 +259,4 @@ async function previewSync(store, view, opts) {
|
|
|
211
259
|
};
|
|
212
260
|
}
|
|
213
261
|
//#endregion
|
|
214
|
-
export {
|
|
262
|
+
export { resolveActions as a, reconcile as i, isRemovalAction as n, runSync as o, previewSync as r, computeSampleWarnings as t };
|
package/vite.d.cts
CHANGED
package/vite.d.mts
CHANGED