clivly 0.2.0 → 0.2.2
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 +54 -3
- package/adapter-Cd6D_3JD.d.mts +46 -0
- package/adapter-Cy4q2CLs.d.cts +46 -0
- package/auth-adapter-CBOKDhlc.cjs +11 -0
- package/auth-adapter-CRBW4elj.d.mts +30 -0
- package/auth-adapter-CdLODn2p.d.cts +30 -0
- package/auth-adapter-wqnnEjo3.mjs +6 -0
- package/auth-better-auth.cjs +28 -0
- package/auth-better-auth.d.cts +33 -0
- package/auth-better-auth.d.mts +33 -0
- package/auth-better-auth.mjs +26 -0
- package/auth-clerk.cjs +34 -0
- package/auth-clerk.d.cts +32 -0
- package/auth-clerk.d.mts +32 -0
- package/auth-clerk.mjs +32 -0
- package/auth-workos.cjs +47 -0
- package/auth-workos.d.cts +30 -0
- package/auth-workos.d.mts +30 -0
- package/auth-workos.mjs +45 -0
- package/core-adapter.cjs +0 -0
- package/core-adapter.d.cts +2 -0
- package/core-adapter.d.mts +2 -0
- package/core-adapter.mjs +1 -0
- package/core-auth-adapter.cjs +3 -0
- package/core-auth-adapter.d.cts +2 -0
- package/core-auth-adapter.d.mts +2 -0
- package/core-auth-adapter.mjs +2 -0
- package/core-drizzle.cjs +32 -0
- package/core-drizzle.d.cts +21 -0
- package/core-drizzle.d.mts +21 -0
- package/core-drizzle.mjs +31 -0
- package/core-entity-config.cjs +13 -0
- package/core-entity-config.d.cts +2 -0
- package/core-entity-config.d.mts +2 -0
- package/core-entity-config.mjs +2 -0
- package/core-entity-heuristics.cjs +442 -0
- package/core-entity-heuristics.d.cts +68 -0
- package/core-entity-heuristics.d.mts +68 -0
- package/core-entity-heuristics.mjs +431 -0
- package/core-mapping-form.cjs +5 -0
- package/core-mapping-form.d.cts +2 -0
- package/core-mapping-form.d.mts +2 -0
- package/core-mapping-form.mjs +2 -0
- package/core-mapping-score.cjs +12 -0
- package/core-mapping-score.d.cts +2 -0
- package/core-mapping-score.d.mts +2 -0
- package/core-mapping-score.mjs +2 -0
- package/core-mappings-config.cjs +3 -0
- package/core-mappings-config.d.cts +2 -0
- package/core-mappings-config.d.mts +2 -0
- package/core-mappings-config.mjs +2 -0
- package/core-sync-engine.cjs +6 -0
- package/core-sync-engine.d.cts +2 -0
- package/core-sync-engine.d.mts +2 -0
- package/core-sync-engine.mjs +2 -0
- package/core-types.cjs +0 -0
- package/core-types.d.cts +2 -0
- package/core-types.d.mts +2 -0
- package/core-types.mjs +1 -0
- package/core-view-compiler.cjs +5 -0
- package/core-view-compiler.d.cts +2 -0
- package/core-view-compiler.d.mts +2 -0
- package/core-view-compiler.mjs +2 -0
- package/core.cjs +27 -0
- package/core.d.cts +9 -0
- package/core.d.mts +9 -0
- package/core.mjs +7 -0
- package/drizzle-meta-BGn0BqGb.cjs +9 -0
- package/drizzle-meta-CnC336L-.mjs +4 -0
- package/drizzle.cjs +107 -0
- package/drizzle.d.cts +28 -0
- package/drizzle.d.mts +28 -0
- package/drizzle.mjs +105 -0
- package/entity-config-Cvjo1cpv.cjs +4598 -0
- package/entity-config-D1DFyeUp.d.mts +2403 -0
- package/entity-config-DoR0khce.mjs +4533 -0
- package/entity-config-GczjD3y8.d.cts +2403 -0
- package/index.cjs +961 -0
- package/index.d.cts +57 -0
- package/index.d.mts +57 -0
- package/index.mjs +960 -0
- package/mapping-form-BHtPexzI.mjs +47 -0
- package/mapping-form-CUlFVfG8.d.cts +35 -0
- package/mapping-form-DdmDebC3.cjs +64 -0
- package/mapping-form-kjSiJaCX.d.mts +35 -0
- package/mapping-score-A2lRnlpk.d.mts +49 -0
- package/mapping-score-BJNe0lFn.mjs +87 -0
- package/mapping-score-BcjC0ZQs.d.cts +49 -0
- package/mapping-score-D9gfhtRu.cjs +146 -0
- package/mappings-config-CYPrzB3K.mjs +99 -0
- package/mappings-config-DTu_2cyx.cjs +104 -0
- package/mappings-config-DjRLCb2Q.d.cts +42 -0
- package/mappings-config-GxiYsSBQ.d.mts +42 -0
- package/namespace-probe-BtS0nXcr.d.cts +299 -0
- package/namespace-probe-CYMie-BI.d.mts +299 -0
- package/package.json +244 -21
- package/sdk-BZUn6wzs.mjs +764 -0
- package/sdk-CP_mYZa5.cjs +793 -0
- package/sdk.cjs +7 -0
- package/sdk.d.cts +33 -0
- package/sdk.d.mts +33 -0
- package/sdk.mjs +2 -0
- package/sync-engine-DUc0qWgo.cjs +230 -0
- package/sync-engine-DWnQPjV3.d.mts +183 -0
- package/sync-engine-DedXngop.mjs +207 -0
- package/sync-engine-uhfzKM-e.d.cts +183 -0
- package/types-BpsKVccL.d.cts +159 -0
- package/types-_qbJXMt_.d.mts +159 -0
- package/view-compiler-B4UYTvRM.d.cts +71 -0
- package/view-compiler-BHzbI9b7.cjs +202 -0
- package/view-compiler-CosFGar8.mjs +185 -0
- package/view-compiler-i_xFf-L0.d.mts +71 -0
- package/vite.cjs +38 -0
- package/vite.d.cts +23 -0
- package/vite.d.mts +23 -0
- package/vite.mjs +37 -0
- package/dist/index.mjs +0 -542
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { n as compileEntityViews } from "./view-compiler-CosFGar8.mjs";
|
|
2
|
+
//#region ../core/src/sync-engine.ts
|
|
3
|
+
/**
|
|
4
|
+
* Diff a view's rows against the current mirror population for one entity.
|
|
5
|
+
*
|
|
6
|
+
* - source row with no mirror row → create
|
|
7
|
+
* - source row with a live mirror row → update (overwrite identity fields)
|
|
8
|
+
* - source row with an archived mirror → restore (re-activate + update)
|
|
9
|
+
* - live mirror row absent from source → archive (soft delete, keep history)
|
|
10
|
+
* - archived mirror row absent from source → no-op
|
|
11
|
+
*
|
|
12
|
+
* Source rows are de-duplicated by the reconciliation key (first wins). In
|
|
13
|
+
* simple mode (default) the key is `sourceRef`. In composite mode — used for
|
|
14
|
+
* `through` (join-table) entities, where the compiled view fans a person out
|
|
15
|
+
* into one row per membership — the key is `(sourceRef, companyRef)`, so a
|
|
16
|
+
* person in several companies materializes one mirror row per company and a
|
|
17
|
+
* single membership leaving archives only that one row.
|
|
18
|
+
*/
|
|
19
|
+
function reconcile(source, mirror, opts = {}) {
|
|
20
|
+
const keyOf = (sourceRef, companyRef) => opts.composite ? `${sourceRef}\0${companyRef ?? ""}` : sourceRef;
|
|
21
|
+
const seen = /* @__PURE__ */ new Set();
|
|
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();
|
|
32
|
+
const actions = [];
|
|
33
|
+
for (const row of rows) {
|
|
34
|
+
const companyRef = row.companyRef ?? null;
|
|
35
|
+
const key = keyOf(row.sourceRef, companyRef);
|
|
36
|
+
liveKeys.add(key);
|
|
37
|
+
const existing = byKey.get(key);
|
|
38
|
+
if (!existing) actions.push({
|
|
39
|
+
kind: "create",
|
|
40
|
+
sourceRef: row.sourceRef,
|
|
41
|
+
fields: row.fields,
|
|
42
|
+
companyRef
|
|
43
|
+
});
|
|
44
|
+
else if (existing.archivedAt) actions.push({
|
|
45
|
+
kind: "restore",
|
|
46
|
+
id: existing.id,
|
|
47
|
+
sourceRef: row.sourceRef,
|
|
48
|
+
fields: row.fields,
|
|
49
|
+
companyRef
|
|
50
|
+
});
|
|
51
|
+
else actions.push({
|
|
52
|
+
kind: "update",
|
|
53
|
+
id: existing.id,
|
|
54
|
+
sourceRef: row.sourceRef,
|
|
55
|
+
fields: row.fields,
|
|
56
|
+
companyRef
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
for (const m of mirror) {
|
|
60
|
+
const key = keyOf(m.sourceRef, m.companyRef);
|
|
61
|
+
if (!(liveKeys.has(key) || m.archivedAt)) actions.push({
|
|
62
|
+
kind: "archive",
|
|
63
|
+
id: m.id,
|
|
64
|
+
sourceRef: m.sourceRef
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return actions;
|
|
68
|
+
}
|
|
69
|
+
const CONCEPT_ORDER = {
|
|
70
|
+
company: 0,
|
|
71
|
+
contact: 1
|
|
72
|
+
};
|
|
73
|
+
/** Companies before contacts, so contact company_refs resolve to a live mirror. */
|
|
74
|
+
function syncOrder(a, b) {
|
|
75
|
+
return (CONCEPT_ORDER[a.concept] ?? 99) - (CONCEPT_ORDER[b.concept] ?? 99);
|
|
76
|
+
}
|
|
77
|
+
async function resolveActions(store, actions) {
|
|
78
|
+
const refs = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const action of actions) if (action.kind !== "archive" && action.companyRef) refs.add(action.companyRef);
|
|
80
|
+
const resolved = refs.size ? await store.resolveCompanyRefs([...refs]) : /* @__PURE__ */ new Map();
|
|
81
|
+
return actions.map((action) => {
|
|
82
|
+
if (action.kind === "archive") return action;
|
|
83
|
+
const companyId = action.companyRef ? resolved.get(action.companyRef) ?? null : null;
|
|
84
|
+
return {
|
|
85
|
+
kind: action.kind,
|
|
86
|
+
id: "id" in action ? action.id : "",
|
|
87
|
+
sourceRef: action.sourceRef,
|
|
88
|
+
fields: action.fields,
|
|
89
|
+
companyId,
|
|
90
|
+
companySourceRef: action.companyRef ?? null
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function emptyTally() {
|
|
95
|
+
return {
|
|
96
|
+
create: 0,
|
|
97
|
+
update: 0,
|
|
98
|
+
restore: 0,
|
|
99
|
+
archive: 0
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
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
|
+
* Run a full sync: compile the config's views, then reconcile + persist each,
|
|
113
|
+
* companies first. Returns per-entity and total action counts.
|
|
114
|
+
*/
|
|
115
|
+
async function runSync(config, store, options = {}) {
|
|
116
|
+
const allViews = compileEntityViews(config, options).sort(syncOrder);
|
|
117
|
+
const views = options.sample ? allViews.filter((v) => v.entityKey === options.sample?.entityKey) : allViews;
|
|
118
|
+
const entities = [];
|
|
119
|
+
const totals = {
|
|
120
|
+
created: 0,
|
|
121
|
+
updated: 0,
|
|
122
|
+
restored: 0,
|
|
123
|
+
archived: 0
|
|
124
|
+
};
|
|
125
|
+
for (const view of views) {
|
|
126
|
+
const objectTypeId = config.entities[view.entityKey]?.targetObjectTypeId;
|
|
127
|
+
const [source, mirror] = await Promise.all([store.readView(view, options.sample ? { limit: 1 } : void 0), store.listMirror({
|
|
128
|
+
concept: view.concept,
|
|
129
|
+
role: view.role,
|
|
130
|
+
objectTypeId
|
|
131
|
+
})]);
|
|
132
|
+
const actions = reconcile(source, mirror, { composite: isCompositeEntity(config, view.entityKey) }).filter((a) => !(options.sample && a.kind === "archive"));
|
|
133
|
+
const persistActions = await resolveActions(store, actions);
|
|
134
|
+
await store.persist({
|
|
135
|
+
concept: view.concept,
|
|
136
|
+
role: view.role,
|
|
137
|
+
objectTypeId,
|
|
138
|
+
actions: persistActions
|
|
139
|
+
});
|
|
140
|
+
const counts = emptyTally();
|
|
141
|
+
for (const action of actions) counts[action.kind] += 1;
|
|
142
|
+
const result = {
|
|
143
|
+
entityKey: view.entityKey,
|
|
144
|
+
concept: view.concept,
|
|
145
|
+
role: view.role,
|
|
146
|
+
created: counts.create,
|
|
147
|
+
updated: counts.update,
|
|
148
|
+
restored: counts.restore,
|
|
149
|
+
archived: counts.archive
|
|
150
|
+
};
|
|
151
|
+
entities.push(result);
|
|
152
|
+
totals.created += result.created;
|
|
153
|
+
totals.updated += result.updated;
|
|
154
|
+
totals.restored += result.restored;
|
|
155
|
+
totals.archived += result.archived;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
entities,
|
|
159
|
+
totals
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function isEmptyValue(value) {
|
|
163
|
+
return value === null || value === void 0 || value === "";
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Sample-scoped data-quality warnings over projected preview rows. Each mapped
|
|
167
|
+
* (or required-but-unmapped) field yields at most one warning, by precedence:
|
|
168
|
+
* a required field empty in ≥1 row → `K/N` form; an optional field empty in
|
|
169
|
+
* ALL rows → all-empty form; zero source rows → a single table-level warning.
|
|
170
|
+
*/
|
|
171
|
+
function computeSampleWarnings(rows, requiredFields, sourceTable) {
|
|
172
|
+
if (rows.length === 0) return [{ message: `no rows found in ${sourceTable}` }];
|
|
173
|
+
const required = new Set(requiredFields);
|
|
174
|
+
const fields = new Set(requiredFields);
|
|
175
|
+
for (const row of rows) for (const key of Object.keys(row.fields)) fields.add(key);
|
|
176
|
+
const warnings = [];
|
|
177
|
+
for (const field of [...fields].sort()) {
|
|
178
|
+
const emptyCount = rows.filter((row) => isEmptyValue(row.fields[field])).length;
|
|
179
|
+
if (required.has(field)) {
|
|
180
|
+
if (emptyCount > 0) warnings.push({
|
|
181
|
+
field,
|
|
182
|
+
message: `${field} empty in ${emptyCount}/${rows.length} sampled`
|
|
183
|
+
});
|
|
184
|
+
} else if (emptyCount === rows.length) warnings.push({
|
|
185
|
+
field,
|
|
186
|
+
message: `${field} is empty in all ${rows.length} sampled rows`
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return warnings;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Zero-write preview: read ≤`limit` rows through the compiled view (a pure
|
|
193
|
+
* SELECT whose SQL already projects each row into its mapped fields) and
|
|
194
|
+
* compute sample warnings. Calls ONLY `store.readView` — never a write path —
|
|
195
|
+
* so it can never mutate the mirror. The distinct, write-one-row sibling is
|
|
196
|
+
* `runSync`'s sample mode (T13).
|
|
197
|
+
*/
|
|
198
|
+
async function previewSync(store, view, opts) {
|
|
199
|
+
const rows = await store.readView(view, { limit: opts.limit });
|
|
200
|
+
return {
|
|
201
|
+
rows,
|
|
202
|
+
sampledCount: rows.length,
|
|
203
|
+
warnings: computeSampleWarnings(rows, opts.requiredFields, opts.sourceTable)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
export { runSync as i, previewSync as n, reconcile as r, computeSampleWarnings as t };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { a as ClivlyEntitiesConfig } from "./entity-config-GczjD3y8.cjs";
|
|
2
|
+
import { n as CompiledView, t as CompileOptions } from "./view-compiler-B4UYTvRM.cjs";
|
|
3
|
+
|
|
4
|
+
//#region ../core/src/sync-engine.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Sync engine (Phase 3) — materializes the read-only entity views into Clivly's
|
|
7
|
+
* own mirror tables (`crm_companies`, `crm_contacts`).
|
|
8
|
+
*
|
|
9
|
+
* The reconciliation is the heart of the design: rows present in a view are
|
|
10
|
+
* created or updated in the mirror; rows that *leave* a view (e.g. an owner
|
|
11
|
+
* demoted to a member) are **archived, not deleted**, so their deals/activities
|
|
12
|
+
* survive. Identity fields are host-owned and overwritten each run; Clivly-owned
|
|
13
|
+
* CRM fields are never touched here.
|
|
14
|
+
*
|
|
15
|
+
* `reconcile()` is a pure function and independently testable. `runSync()`
|
|
16
|
+
* orchestrates: compile views → for each (companies first, so contact
|
|
17
|
+
* `company_ref`s resolve to a live `crm_companies.id`) read the view, reconcile
|
|
18
|
+
* against the current mirror, resolve company refs, and persist. All DB I/O is
|
|
19
|
+
* behind the `SyncStore` port — core stays ORM-agnostic; the SDK/drizzle
|
|
20
|
+
* package provides the concrete store.
|
|
21
|
+
*/
|
|
22
|
+
/** A row read from a compiled view (what Postgres returns). */
|
|
23
|
+
interface SourceRow {
|
|
24
|
+
/** Host-side company key from `<relationship>_ref`; resolved before persist. */
|
|
25
|
+
companyRef?: string | null;
|
|
26
|
+
/** Mapped identity fields (clivly field → value). */
|
|
27
|
+
fields: Record<string, string | null>;
|
|
28
|
+
/** The source primary key — becomes the mirror row's `source_ref`. */
|
|
29
|
+
sourceRef: string;
|
|
30
|
+
}
|
|
31
|
+
/** An existing mirror row, as far as reconciliation needs to know. */
|
|
32
|
+
interface MirrorRecord {
|
|
33
|
+
archivedAt: Date | null;
|
|
34
|
+
/** Host-side company key; only consulted in composite (join-table) mode. */
|
|
35
|
+
companyRef: string | null;
|
|
36
|
+
id: string;
|
|
37
|
+
sourceRef: string;
|
|
38
|
+
}
|
|
39
|
+
type SyncAction = {
|
|
40
|
+
kind: "create";
|
|
41
|
+
sourceRef: string;
|
|
42
|
+
fields: Record<string, string | null>;
|
|
43
|
+
companyRef: string | null;
|
|
44
|
+
} | {
|
|
45
|
+
kind: "update";
|
|
46
|
+
id: string;
|
|
47
|
+
sourceRef: string;
|
|
48
|
+
fields: Record<string, string | null>;
|
|
49
|
+
companyRef: string | null;
|
|
50
|
+
} | {
|
|
51
|
+
kind: "restore";
|
|
52
|
+
id: string;
|
|
53
|
+
sourceRef: string;
|
|
54
|
+
fields: Record<string, string | null>;
|
|
55
|
+
companyRef: string | null;
|
|
56
|
+
} | {
|
|
57
|
+
kind: "archive";
|
|
58
|
+
id: string;
|
|
59
|
+
sourceRef: string;
|
|
60
|
+
};
|
|
61
|
+
/** A persist action with the company ref resolved to a `crm_companies.id`. */
|
|
62
|
+
type PersistAction = {
|
|
63
|
+
kind: "create";
|
|
64
|
+
sourceRef: string;
|
|
65
|
+
fields: Record<string, string | null>;
|
|
66
|
+
companyId: string | null;
|
|
67
|
+
companySourceRef: string | null;
|
|
68
|
+
} | {
|
|
69
|
+
kind: "update";
|
|
70
|
+
id: string;
|
|
71
|
+
sourceRef: string;
|
|
72
|
+
fields: Record<string, string | null>;
|
|
73
|
+
companyId: string | null;
|
|
74
|
+
companySourceRef: string | null;
|
|
75
|
+
} | {
|
|
76
|
+
kind: "restore";
|
|
77
|
+
id: string;
|
|
78
|
+
sourceRef: string;
|
|
79
|
+
fields: Record<string, string | null>;
|
|
80
|
+
companyId: string | null;
|
|
81
|
+
companySourceRef: string | null;
|
|
82
|
+
} | {
|
|
83
|
+
kind: "archive";
|
|
84
|
+
id: string;
|
|
85
|
+
sourceRef: string;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Diff a view's rows against the current mirror population for one entity.
|
|
89
|
+
*
|
|
90
|
+
* - source row with no mirror row → create
|
|
91
|
+
* - source row with a live mirror row → update (overwrite identity fields)
|
|
92
|
+
* - source row with an archived mirror → restore (re-activate + update)
|
|
93
|
+
* - live mirror row absent from source → archive (soft delete, keep history)
|
|
94
|
+
* - archived mirror row absent from source → no-op
|
|
95
|
+
*
|
|
96
|
+
* Source rows are de-duplicated by the reconciliation key (first wins). In
|
|
97
|
+
* simple mode (default) the key is `sourceRef`. In composite mode — used for
|
|
98
|
+
* `through` (join-table) entities, where the compiled view fans a person out
|
|
99
|
+
* into one row per membership — the key is `(sourceRef, companyRef)`, so a
|
|
100
|
+
* person in several companies materializes one mirror row per company and a
|
|
101
|
+
* single membership leaving archives only that one row.
|
|
102
|
+
*/
|
|
103
|
+
declare function reconcile(source: SourceRow[], mirror: MirrorRecord[], opts?: {
|
|
104
|
+
composite?: boolean;
|
|
105
|
+
}): SyncAction[];
|
|
106
|
+
/** DB I/O port. The concrete store lives in the SDK/drizzle package. */
|
|
107
|
+
interface SyncStore {
|
|
108
|
+
/** Current mirror rows for a concept + role (including archived). */
|
|
109
|
+
listMirror(input: {
|
|
110
|
+
concept: string;
|
|
111
|
+
role: string; /** Target custom-object type id, for `concept: "custom"` entities. */
|
|
112
|
+
objectTypeId?: string;
|
|
113
|
+
}): Promise<MirrorRecord[]>;
|
|
114
|
+
/** Apply the resolved actions to the mirror table for this concept + role. */
|
|
115
|
+
persist(input: {
|
|
116
|
+
concept: string;
|
|
117
|
+
role: string; /** Target custom-object type id, for `concept: "custom"` entities. */
|
|
118
|
+
objectTypeId?: string;
|
|
119
|
+
actions: PersistAction[];
|
|
120
|
+
}): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Execute the view's SQL and return its rows. `opts.limit`, when given,
|
|
123
|
+
* caps the number of rows returned (used by `runSync`'s sample mode).
|
|
124
|
+
*/
|
|
125
|
+
readView(view: CompiledView, opts?: {
|
|
126
|
+
limit?: number;
|
|
127
|
+
}): Promise<SourceRow[]>;
|
|
128
|
+
/** Map host-side company refs → `crm_companies.id` (already-synced companies only). */
|
|
129
|
+
resolveCompanyRefs(refs: string[]): Promise<Map<string, string>>;
|
|
130
|
+
}
|
|
131
|
+
interface EntitySyncResult {
|
|
132
|
+
archived: number;
|
|
133
|
+
concept: string;
|
|
134
|
+
created: number;
|
|
135
|
+
entityKey: string;
|
|
136
|
+
restored: number;
|
|
137
|
+
role: string;
|
|
138
|
+
updated: number;
|
|
139
|
+
}
|
|
140
|
+
interface SyncResult {
|
|
141
|
+
entities: EntitySyncResult[];
|
|
142
|
+
totals: {
|
|
143
|
+
created: number;
|
|
144
|
+
updated: number;
|
|
145
|
+
restored: number;
|
|
146
|
+
archived: number;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Run a full sync: compile the config's views, then reconcile + persist each,
|
|
151
|
+
* companies first. Returns per-entity and total action counts.
|
|
152
|
+
*/
|
|
153
|
+
declare function runSync(config: ClivlyEntitiesConfig, store: SyncStore, options?: CompileOptions): Promise<SyncResult>;
|
|
154
|
+
interface PreviewWarning {
|
|
155
|
+
field?: string;
|
|
156
|
+
message: string;
|
|
157
|
+
}
|
|
158
|
+
interface PreviewResult {
|
|
159
|
+
rows: SourceRow[];
|
|
160
|
+
sampledCount: number;
|
|
161
|
+
warnings: PreviewWarning[];
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Sample-scoped data-quality warnings over projected preview rows. Each mapped
|
|
165
|
+
* (or required-but-unmapped) field yields at most one warning, by precedence:
|
|
166
|
+
* a required field empty in ≥1 row → `K/N` form; an optional field empty in
|
|
167
|
+
* ALL rows → all-empty form; zero source rows → a single table-level warning.
|
|
168
|
+
*/
|
|
169
|
+
declare function computeSampleWarnings(rows: SourceRow[], requiredFields: string[], sourceTable: string): PreviewWarning[];
|
|
170
|
+
/**
|
|
171
|
+
* Zero-write preview: read ≤`limit` rows through the compiled view (a pure
|
|
172
|
+
* SELECT whose SQL already projects each row into its mapped fields) and
|
|
173
|
+
* compute sample warnings. Calls ONLY `store.readView` — never a write path —
|
|
174
|
+
* so it can never mutate the mirror. The distinct, write-one-row sibling is
|
|
175
|
+
* `runSync`'s sample mode (T13).
|
|
176
|
+
*/
|
|
177
|
+
declare function previewSync(store: SyncStore, view: CompiledView, opts: {
|
|
178
|
+
limit: number;
|
|
179
|
+
requiredFields: string[];
|
|
180
|
+
sourceTable: string;
|
|
181
|
+
}): Promise<PreviewResult>;
|
|
182
|
+
//#endregion
|
|
183
|
+
export { PreviewWarning as a, SyncResult as c, previewSync as d, reconcile as f, PreviewResult as i, SyncStore as l, MirrorRecord as n, SourceRow as o, runSync as p, PersistAction as r, SyncAction as s, EntitySyncResult as t, computeSampleWarnings as u };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
//#region ../core/src/types.d.ts
|
|
2
|
+
type ContactStatus = "lead" | "active" | "customer" | "churned";
|
|
3
|
+
type DealStage = "lead" | "qualified" | "proposal" | "negotiation" | "won" | "lost";
|
|
4
|
+
type TaskStatus = "todo" | "in_progress" | "done" | "cancelled";
|
|
5
|
+
type TaskPriority = "low" | "medium" | "high";
|
|
6
|
+
type TaskEntityType = "contact" | "company" | "deal";
|
|
7
|
+
type ActivityType = "note" | "email" | "task" | "deal" | "call";
|
|
8
|
+
type CrmRole = "owner" | "admin" | "member" | "developer" | "support_agent";
|
|
9
|
+
interface Contact {
|
|
10
|
+
companyId: string | null;
|
|
11
|
+
createdAt: Date;
|
|
12
|
+
email: string;
|
|
13
|
+
id: string;
|
|
14
|
+
lastActivityAt: Date | null;
|
|
15
|
+
name: string;
|
|
16
|
+
orgId: string;
|
|
17
|
+
status: ContactStatus;
|
|
18
|
+
title: string | null;
|
|
19
|
+
}
|
|
20
|
+
interface Company {
|
|
21
|
+
createdAt: Date;
|
|
22
|
+
domain: string | null;
|
|
23
|
+
id: string;
|
|
24
|
+
industry: string | null;
|
|
25
|
+
name: string;
|
|
26
|
+
orgId: string;
|
|
27
|
+
size: string | null;
|
|
28
|
+
}
|
|
29
|
+
interface Deal {
|
|
30
|
+
closeDate: Date | null;
|
|
31
|
+
contactId: string | null;
|
|
32
|
+
createdAt: Date;
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
orgId: string;
|
|
36
|
+
owner: string | null;
|
|
37
|
+
position: number | null;
|
|
38
|
+
stage: DealStage;
|
|
39
|
+
value: number;
|
|
40
|
+
}
|
|
41
|
+
interface Task {
|
|
42
|
+
archivedAt: Date | null;
|
|
43
|
+
assignee: string | null;
|
|
44
|
+
completedAt: Date | null;
|
|
45
|
+
contactId: string | null;
|
|
46
|
+
createdAt: Date;
|
|
47
|
+
createdBy: string | null;
|
|
48
|
+
description: string | null;
|
|
49
|
+
dueAt: Date | null;
|
|
50
|
+
entityId: string | null;
|
|
51
|
+
entityType: TaskEntityType | null;
|
|
52
|
+
id: string;
|
|
53
|
+
orgId: string;
|
|
54
|
+
priority: TaskPriority;
|
|
55
|
+
status: TaskStatus;
|
|
56
|
+
title: string;
|
|
57
|
+
}
|
|
58
|
+
interface Note {
|
|
59
|
+
archivedAt: Date | null;
|
|
60
|
+
author: string | null;
|
|
61
|
+
body: string;
|
|
62
|
+
contactId: string | null;
|
|
63
|
+
createdAt: Date;
|
|
64
|
+
createdBy: string | null;
|
|
65
|
+
entityId: string | null;
|
|
66
|
+
entityType: TaskEntityType | null;
|
|
67
|
+
id: string;
|
|
68
|
+
isTemplate: boolean;
|
|
69
|
+
orgId: string;
|
|
70
|
+
title: string;
|
|
71
|
+
updatedAt: Date;
|
|
72
|
+
}
|
|
73
|
+
interface Activity {
|
|
74
|
+
actor: string | null;
|
|
75
|
+
contactId: string | null;
|
|
76
|
+
createdAt: Date;
|
|
77
|
+
id: string;
|
|
78
|
+
orgId: string;
|
|
79
|
+
summary: string;
|
|
80
|
+
type: ActivityType;
|
|
81
|
+
}
|
|
82
|
+
interface CreateContactInput {
|
|
83
|
+
companyId?: string;
|
|
84
|
+
email: string;
|
|
85
|
+
name: string;
|
|
86
|
+
status?: ContactStatus;
|
|
87
|
+
title?: string;
|
|
88
|
+
}
|
|
89
|
+
interface UpdateContactInput {
|
|
90
|
+
companyId?: string | null;
|
|
91
|
+
email?: string;
|
|
92
|
+
name?: string;
|
|
93
|
+
status?: ContactStatus;
|
|
94
|
+
title?: string | null;
|
|
95
|
+
}
|
|
96
|
+
interface CreateCompanyInput {
|
|
97
|
+
domain?: string;
|
|
98
|
+
industry?: string;
|
|
99
|
+
name: string;
|
|
100
|
+
size?: string;
|
|
101
|
+
}
|
|
102
|
+
interface CreateDealInput {
|
|
103
|
+
contactId?: string;
|
|
104
|
+
name: string;
|
|
105
|
+
owner?: string;
|
|
106
|
+
stage?: DealStage;
|
|
107
|
+
value?: number;
|
|
108
|
+
}
|
|
109
|
+
interface UpdateDealInput {
|
|
110
|
+
closeDate?: Date | null;
|
|
111
|
+
contactId?: string | null;
|
|
112
|
+
name?: string;
|
|
113
|
+
owner?: string | null;
|
|
114
|
+
stage?: DealStage;
|
|
115
|
+
value?: number;
|
|
116
|
+
}
|
|
117
|
+
interface CreateTaskInput {
|
|
118
|
+
assignee?: string;
|
|
119
|
+
createdBy?: string;
|
|
120
|
+
description?: string;
|
|
121
|
+
dueAt?: Date;
|
|
122
|
+
entityId?: string;
|
|
123
|
+
entityType?: TaskEntityType;
|
|
124
|
+
priority?: TaskPriority;
|
|
125
|
+
title: string;
|
|
126
|
+
}
|
|
127
|
+
interface UpdateTaskInput {
|
|
128
|
+
assignee?: string | null;
|
|
129
|
+
description?: string | null;
|
|
130
|
+
dueAt?: Date | null;
|
|
131
|
+
entityId?: string | null;
|
|
132
|
+
entityType?: TaskEntityType | null;
|
|
133
|
+
priority?: TaskPriority;
|
|
134
|
+
title?: string;
|
|
135
|
+
}
|
|
136
|
+
interface CreateActivityInput {
|
|
137
|
+
actor?: string;
|
|
138
|
+
contactId?: string;
|
|
139
|
+
summary: string;
|
|
140
|
+
type: ActivityType;
|
|
141
|
+
}
|
|
142
|
+
type RecordSource = "all" | "synced";
|
|
143
|
+
interface ContactFilters {
|
|
144
|
+
companyId?: string;
|
|
145
|
+
search?: string;
|
|
146
|
+
source?: RecordSource;
|
|
147
|
+
status?: ContactStatus;
|
|
148
|
+
}
|
|
149
|
+
interface TaskFilters {
|
|
150
|
+
assignee?: string;
|
|
151
|
+
entityId?: string;
|
|
152
|
+
entityType?: TaskEntityType;
|
|
153
|
+
includeArchived?: boolean;
|
|
154
|
+
includeCompleted?: boolean;
|
|
155
|
+
priority?: TaskPriority;
|
|
156
|
+
status?: TaskStatus;
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
export { UpdateDealInput as C, UpdateContactInput as S, Task as _, ContactFilters as a, TaskPriority as b, CreateCompanyInput as c, CreateTaskInput as d, CrmRole as f, RecordSource as g, Note as h, Contact as i, CreateContactInput as l, DealStage as m, ActivityType as n, ContactStatus as o, Deal as p, Company as r, CreateActivityInput as s, Activity as t, CreateDealInput as u, TaskEntityType as v, UpdateTaskInput as w, TaskStatus as x, TaskFilters as y };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
//#region ../core/src/types.d.ts
|
|
2
|
+
type ContactStatus = "lead" | "active" | "customer" | "churned";
|
|
3
|
+
type DealStage = "lead" | "qualified" | "proposal" | "negotiation" | "won" | "lost";
|
|
4
|
+
type TaskStatus = "todo" | "in_progress" | "done" | "cancelled";
|
|
5
|
+
type TaskPriority = "low" | "medium" | "high";
|
|
6
|
+
type TaskEntityType = "contact" | "company" | "deal";
|
|
7
|
+
type ActivityType = "note" | "email" | "task" | "deal" | "call";
|
|
8
|
+
type CrmRole = "owner" | "admin" | "member" | "developer" | "support_agent";
|
|
9
|
+
interface Contact {
|
|
10
|
+
companyId: string | null;
|
|
11
|
+
createdAt: Date;
|
|
12
|
+
email: string;
|
|
13
|
+
id: string;
|
|
14
|
+
lastActivityAt: Date | null;
|
|
15
|
+
name: string;
|
|
16
|
+
orgId: string;
|
|
17
|
+
status: ContactStatus;
|
|
18
|
+
title: string | null;
|
|
19
|
+
}
|
|
20
|
+
interface Company {
|
|
21
|
+
createdAt: Date;
|
|
22
|
+
domain: string | null;
|
|
23
|
+
id: string;
|
|
24
|
+
industry: string | null;
|
|
25
|
+
name: string;
|
|
26
|
+
orgId: string;
|
|
27
|
+
size: string | null;
|
|
28
|
+
}
|
|
29
|
+
interface Deal {
|
|
30
|
+
closeDate: Date | null;
|
|
31
|
+
contactId: string | null;
|
|
32
|
+
createdAt: Date;
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
orgId: string;
|
|
36
|
+
owner: string | null;
|
|
37
|
+
position: number | null;
|
|
38
|
+
stage: DealStage;
|
|
39
|
+
value: number;
|
|
40
|
+
}
|
|
41
|
+
interface Task {
|
|
42
|
+
archivedAt: Date | null;
|
|
43
|
+
assignee: string | null;
|
|
44
|
+
completedAt: Date | null;
|
|
45
|
+
contactId: string | null;
|
|
46
|
+
createdAt: Date;
|
|
47
|
+
createdBy: string | null;
|
|
48
|
+
description: string | null;
|
|
49
|
+
dueAt: Date | null;
|
|
50
|
+
entityId: string | null;
|
|
51
|
+
entityType: TaskEntityType | null;
|
|
52
|
+
id: string;
|
|
53
|
+
orgId: string;
|
|
54
|
+
priority: TaskPriority;
|
|
55
|
+
status: TaskStatus;
|
|
56
|
+
title: string;
|
|
57
|
+
}
|
|
58
|
+
interface Note {
|
|
59
|
+
archivedAt: Date | null;
|
|
60
|
+
author: string | null;
|
|
61
|
+
body: string;
|
|
62
|
+
contactId: string | null;
|
|
63
|
+
createdAt: Date;
|
|
64
|
+
createdBy: string | null;
|
|
65
|
+
entityId: string | null;
|
|
66
|
+
entityType: TaskEntityType | null;
|
|
67
|
+
id: string;
|
|
68
|
+
isTemplate: boolean;
|
|
69
|
+
orgId: string;
|
|
70
|
+
title: string;
|
|
71
|
+
updatedAt: Date;
|
|
72
|
+
}
|
|
73
|
+
interface Activity {
|
|
74
|
+
actor: string | null;
|
|
75
|
+
contactId: string | null;
|
|
76
|
+
createdAt: Date;
|
|
77
|
+
id: string;
|
|
78
|
+
orgId: string;
|
|
79
|
+
summary: string;
|
|
80
|
+
type: ActivityType;
|
|
81
|
+
}
|
|
82
|
+
interface CreateContactInput {
|
|
83
|
+
companyId?: string;
|
|
84
|
+
email: string;
|
|
85
|
+
name: string;
|
|
86
|
+
status?: ContactStatus;
|
|
87
|
+
title?: string;
|
|
88
|
+
}
|
|
89
|
+
interface UpdateContactInput {
|
|
90
|
+
companyId?: string | null;
|
|
91
|
+
email?: string;
|
|
92
|
+
name?: string;
|
|
93
|
+
status?: ContactStatus;
|
|
94
|
+
title?: string | null;
|
|
95
|
+
}
|
|
96
|
+
interface CreateCompanyInput {
|
|
97
|
+
domain?: string;
|
|
98
|
+
industry?: string;
|
|
99
|
+
name: string;
|
|
100
|
+
size?: string;
|
|
101
|
+
}
|
|
102
|
+
interface CreateDealInput {
|
|
103
|
+
contactId?: string;
|
|
104
|
+
name: string;
|
|
105
|
+
owner?: string;
|
|
106
|
+
stage?: DealStage;
|
|
107
|
+
value?: number;
|
|
108
|
+
}
|
|
109
|
+
interface UpdateDealInput {
|
|
110
|
+
closeDate?: Date | null;
|
|
111
|
+
contactId?: string | null;
|
|
112
|
+
name?: string;
|
|
113
|
+
owner?: string | null;
|
|
114
|
+
stage?: DealStage;
|
|
115
|
+
value?: number;
|
|
116
|
+
}
|
|
117
|
+
interface CreateTaskInput {
|
|
118
|
+
assignee?: string;
|
|
119
|
+
createdBy?: string;
|
|
120
|
+
description?: string;
|
|
121
|
+
dueAt?: Date;
|
|
122
|
+
entityId?: string;
|
|
123
|
+
entityType?: TaskEntityType;
|
|
124
|
+
priority?: TaskPriority;
|
|
125
|
+
title: string;
|
|
126
|
+
}
|
|
127
|
+
interface UpdateTaskInput {
|
|
128
|
+
assignee?: string | null;
|
|
129
|
+
description?: string | null;
|
|
130
|
+
dueAt?: Date | null;
|
|
131
|
+
entityId?: string | null;
|
|
132
|
+
entityType?: TaskEntityType | null;
|
|
133
|
+
priority?: TaskPriority;
|
|
134
|
+
title?: string;
|
|
135
|
+
}
|
|
136
|
+
interface CreateActivityInput {
|
|
137
|
+
actor?: string;
|
|
138
|
+
contactId?: string;
|
|
139
|
+
summary: string;
|
|
140
|
+
type: ActivityType;
|
|
141
|
+
}
|
|
142
|
+
type RecordSource = "all" | "synced";
|
|
143
|
+
interface ContactFilters {
|
|
144
|
+
companyId?: string;
|
|
145
|
+
search?: string;
|
|
146
|
+
source?: RecordSource;
|
|
147
|
+
status?: ContactStatus;
|
|
148
|
+
}
|
|
149
|
+
interface TaskFilters {
|
|
150
|
+
assignee?: string;
|
|
151
|
+
entityId?: string;
|
|
152
|
+
entityType?: TaskEntityType;
|
|
153
|
+
includeArchived?: boolean;
|
|
154
|
+
includeCompleted?: boolean;
|
|
155
|
+
priority?: TaskPriority;
|
|
156
|
+
status?: TaskStatus;
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
export { UpdateDealInput as C, UpdateContactInput as S, Task as _, ContactFilters as a, TaskPriority as b, CreateCompanyInput as c, CreateTaskInput as d, CrmRole as f, RecordSource as g, Note as h, Contact as i, CreateContactInput as l, DealStage as m, ActivityType as n, ContactStatus as o, Deal as p, Company as r, CreateActivityInput as s, Activity as t, CreateDealInput as u, TaskEntityType as v, UpdateTaskInput as w, TaskStatus as x, TaskFilters as y };
|