@clivly/core 0.8.0 → 0.9.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/README.md +96 -15
- package/dist/drizzle.cjs +82 -1
- package/dist/drizzle.d.cts +23 -2
- package/dist/drizzle.d.mts +23 -2
- package/dist/drizzle.mjs +81 -2
- package/dist/entity-config-PoOGzvpN.cjs +728 -0
- package/dist/entity-config-Tb5bbKLj.mjs +621 -0
- package/dist/entity-config.cjs +12 -349
- package/dist/entity-config.d.cts +120 -1
- package/dist/entity-config.d.mts +120 -1
- package/dist/entity-config.mjs +1 -338
- package/dist/entity-heuristics.cjs +28 -8
- package/dist/entity-heuristics.d.cts +2 -12
- package/dist/entity-heuristics.d.mts +2 -12
- package/dist/entity-heuristics.mjs +28 -8
- package/dist/env-file.cjs +59 -0
- package/dist/env-file.d.cts +23 -0
- package/dist/env-file.d.mts +23 -0
- package/dist/env-file.mjs +55 -0
- package/dist/index.cjs +83 -2
- package/dist/index.d.cts +73 -3
- package/dist/index.d.mts +73 -3
- package/dist/index.mjs +69 -3
- package/dist/integration-state.cjs +242 -0
- package/dist/integration-state.d.cts +88 -0
- package/dist/integration-state.d.mts +88 -0
- package/dist/integration-state.mjs +232 -0
- package/dist/mappings-config-B0W5bVZj.d.mts +168 -0
- package/dist/mappings-config-BWo4cRDF.d.cts +168 -0
- package/dist/mappings-config.cjs +32 -6
- package/dist/mappings-config.d.cts +1 -40
- package/dist/mappings-config.d.mts +1 -40
- package/dist/mappings-config.mjs +32 -6
- package/dist/projection-BXBkh7qp.cjs +98 -0
- package/dist/projection-C_EMRmZ8.mjs +75 -0
- package/dist/sync-engine.cjs +9 -2
- package/dist/sync-engine.mjs +9 -2
- package/dist/view-compiler-BKrBA_II.mjs +254 -0
- package/dist/view-compiler-BwZ9UgLp.cjs +277 -0
- package/dist/view-compiler.cjs +5 -188
- package/dist/view-compiler.mjs +1 -184
- package/package.json +21 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//#region src/integration-state.d.ts
|
|
2
|
+
declare const INTEGRATION_STEPS: readonly ["credential", "application", "schema", "mapping", "trigger", "sync", "widget"];
|
|
3
|
+
type StepId = (typeof INTEGRATION_STEPS)[number];
|
|
4
|
+
/** `blocked` means "prerequisite unmet", never "broken" — see C-19. */
|
|
5
|
+
type StepState = "done" | "active" | "blocked" | "failed" | "skipped";
|
|
6
|
+
type IntegrationStatus = "not_configured" | "sdk_installed" | "awaiting_connection" | "connected" | "schema_discovered" | "mapping_required" | "trigger_required" | "ready_to_sync" | "syncing" | "healthy" | "warning" | "error" | "disconnected";
|
|
7
|
+
declare const INTEGRATION_STEP_LABELS: Record<StepId, string>;
|
|
8
|
+
/** Each step's prerequisites. Ordering is enforced by a test, not convention. */
|
|
9
|
+
declare const STEP_PREREQUISITES: Record<StepId, readonly StepId[]>;
|
|
10
|
+
declare function stepIndex(id: StepId): number;
|
|
11
|
+
interface RunFact {
|
|
12
|
+
at: number;
|
|
13
|
+
message?: string;
|
|
14
|
+
ok: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface IntegrationFacts {
|
|
17
|
+
discoveredTableCount: number;
|
|
18
|
+
/**
|
|
19
|
+
* Has this app ever checked in? Distinguishes "never installed" from "was
|
|
20
|
+
* working, now silent" — the same absent heartbeat means different things,
|
|
21
|
+
* and only one of them is diagnosed rather than set up.
|
|
22
|
+
*/
|
|
23
|
+
everConnected: boolean;
|
|
24
|
+
hasCredential: boolean;
|
|
25
|
+
lastHeartbeatAt: number | null;
|
|
26
|
+
lastRun: RunFact | null;
|
|
27
|
+
mappedEntityCount: number;
|
|
28
|
+
now: number;
|
|
29
|
+
syncInFlight: boolean;
|
|
30
|
+
/** A trigger URL and secret both exist, so Clivly can ask the app to sync. */
|
|
31
|
+
triggerConfigured: boolean;
|
|
32
|
+
warnings: string[];
|
|
33
|
+
widgetInstalled: boolean;
|
|
34
|
+
}
|
|
35
|
+
interface StepProblem {
|
|
36
|
+
code: string;
|
|
37
|
+
fix: string;
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
interface IntegrationStep {
|
|
41
|
+
blockedBy: StepId | null;
|
|
42
|
+
evidence: string | null;
|
|
43
|
+
id: StepId;
|
|
44
|
+
label: string;
|
|
45
|
+
problem: StepProblem | null;
|
|
46
|
+
state: StepState;
|
|
47
|
+
}
|
|
48
|
+
declare function formatAge(ms: number): string;
|
|
49
|
+
/**
|
|
50
|
+
* Walk the ladder once. A step is `blocked` when an earlier step it needs has
|
|
51
|
+
* not been satisfied — a prerequisite statement, not a verdict about this step
|
|
52
|
+
* — and only the first unsatisfied step is `active`, so exactly one root cause
|
|
53
|
+
* ever surfaces.
|
|
54
|
+
*/
|
|
55
|
+
declare function resolveSteps(facts: IntegrationFacts): IntegrationStep[];
|
|
56
|
+
/**
|
|
57
|
+
* Collapse the ladder to the single status the surfaces switch on. Live
|
|
58
|
+
* conditions — a run in flight, a failure, a heartbeat gone quiet — win over
|
|
59
|
+
* ladder position, because they describe the integration as it is now rather
|
|
60
|
+
* than how far setup once got.
|
|
61
|
+
*/
|
|
62
|
+
declare function deriveStatus(steps: IntegrationStep[], facts: IntegrationFacts): IntegrationStatus;
|
|
63
|
+
interface NextAction {
|
|
64
|
+
command: string | null;
|
|
65
|
+
envFile: string | null;
|
|
66
|
+
label: string;
|
|
67
|
+
}
|
|
68
|
+
interface IntegrationState {
|
|
69
|
+
next: NextAction | null;
|
|
70
|
+
status: IntegrationStatus;
|
|
71
|
+
steps: IntegrationStep[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The command that installs Clivly into a host app. Exported because the connect
|
|
75
|
+
* dialog prints it too, and it must be the same string as the one the ladder
|
|
76
|
+
* recommends — that pairing is what C-05 broke.
|
|
77
|
+
*/
|
|
78
|
+
declare const INSTALL_COMMAND = "npx clivly init";
|
|
79
|
+
/**
|
|
80
|
+
* THE one producer of `next.command`. Six surfaces previously each invented
|
|
81
|
+
* their own "run this next" string, and one of them named a read-only command
|
|
82
|
+
* as the fix for a write problem (C-05). If you need a command for the
|
|
83
|
+
* next-action slot, call this — do not write another one.
|
|
84
|
+
*/
|
|
85
|
+
declare function nextAction(steps: IntegrationStep[], status: IntegrationStatus): NextAction | null;
|
|
86
|
+
declare function computeIntegrationState(facts: IntegrationFacts): IntegrationState;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { INSTALL_COMMAND, INTEGRATION_STEPS, INTEGRATION_STEP_LABELS, IntegrationFacts, IntegrationState, IntegrationStatus, IntegrationStep, NextAction, RunFact, STEP_PREREQUISITES, StepId, StepProblem, StepState, computeIntegrationState, deriveStatus, formatAge, nextAction, resolveSteps, stepIndex };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//#region src/integration-state.d.ts
|
|
2
|
+
declare const INTEGRATION_STEPS: readonly ["credential", "application", "schema", "mapping", "trigger", "sync", "widget"];
|
|
3
|
+
type StepId = (typeof INTEGRATION_STEPS)[number];
|
|
4
|
+
/** `blocked` means "prerequisite unmet", never "broken" — see C-19. */
|
|
5
|
+
type StepState = "done" | "active" | "blocked" | "failed" | "skipped";
|
|
6
|
+
type IntegrationStatus = "not_configured" | "sdk_installed" | "awaiting_connection" | "connected" | "schema_discovered" | "mapping_required" | "trigger_required" | "ready_to_sync" | "syncing" | "healthy" | "warning" | "error" | "disconnected";
|
|
7
|
+
declare const INTEGRATION_STEP_LABELS: Record<StepId, string>;
|
|
8
|
+
/** Each step's prerequisites. Ordering is enforced by a test, not convention. */
|
|
9
|
+
declare const STEP_PREREQUISITES: Record<StepId, readonly StepId[]>;
|
|
10
|
+
declare function stepIndex(id: StepId): number;
|
|
11
|
+
interface RunFact {
|
|
12
|
+
at: number;
|
|
13
|
+
message?: string;
|
|
14
|
+
ok: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface IntegrationFacts {
|
|
17
|
+
discoveredTableCount: number;
|
|
18
|
+
/**
|
|
19
|
+
* Has this app ever checked in? Distinguishes "never installed" from "was
|
|
20
|
+
* working, now silent" — the same absent heartbeat means different things,
|
|
21
|
+
* and only one of them is diagnosed rather than set up.
|
|
22
|
+
*/
|
|
23
|
+
everConnected: boolean;
|
|
24
|
+
hasCredential: boolean;
|
|
25
|
+
lastHeartbeatAt: number | null;
|
|
26
|
+
lastRun: RunFact | null;
|
|
27
|
+
mappedEntityCount: number;
|
|
28
|
+
now: number;
|
|
29
|
+
syncInFlight: boolean;
|
|
30
|
+
/** A trigger URL and secret both exist, so Clivly can ask the app to sync. */
|
|
31
|
+
triggerConfigured: boolean;
|
|
32
|
+
warnings: string[];
|
|
33
|
+
widgetInstalled: boolean;
|
|
34
|
+
}
|
|
35
|
+
interface StepProblem {
|
|
36
|
+
code: string;
|
|
37
|
+
fix: string;
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
interface IntegrationStep {
|
|
41
|
+
blockedBy: StepId | null;
|
|
42
|
+
evidence: string | null;
|
|
43
|
+
id: StepId;
|
|
44
|
+
label: string;
|
|
45
|
+
problem: StepProblem | null;
|
|
46
|
+
state: StepState;
|
|
47
|
+
}
|
|
48
|
+
declare function formatAge(ms: number): string;
|
|
49
|
+
/**
|
|
50
|
+
* Walk the ladder once. A step is `blocked` when an earlier step it needs has
|
|
51
|
+
* not been satisfied — a prerequisite statement, not a verdict about this step
|
|
52
|
+
* — and only the first unsatisfied step is `active`, so exactly one root cause
|
|
53
|
+
* ever surfaces.
|
|
54
|
+
*/
|
|
55
|
+
declare function resolveSteps(facts: IntegrationFacts): IntegrationStep[];
|
|
56
|
+
/**
|
|
57
|
+
* Collapse the ladder to the single status the surfaces switch on. Live
|
|
58
|
+
* conditions — a run in flight, a failure, a heartbeat gone quiet — win over
|
|
59
|
+
* ladder position, because they describe the integration as it is now rather
|
|
60
|
+
* than how far setup once got.
|
|
61
|
+
*/
|
|
62
|
+
declare function deriveStatus(steps: IntegrationStep[], facts: IntegrationFacts): IntegrationStatus;
|
|
63
|
+
interface NextAction {
|
|
64
|
+
command: string | null;
|
|
65
|
+
envFile: string | null;
|
|
66
|
+
label: string;
|
|
67
|
+
}
|
|
68
|
+
interface IntegrationState {
|
|
69
|
+
next: NextAction | null;
|
|
70
|
+
status: IntegrationStatus;
|
|
71
|
+
steps: IntegrationStep[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The command that installs Clivly into a host app. Exported because the connect
|
|
75
|
+
* dialog prints it too, and it must be the same string as the one the ladder
|
|
76
|
+
* recommends — that pairing is what C-05 broke.
|
|
77
|
+
*/
|
|
78
|
+
declare const INSTALL_COMMAND = "npx clivly init";
|
|
79
|
+
/**
|
|
80
|
+
* THE one producer of `next.command`. Six surfaces previously each invented
|
|
81
|
+
* their own "run this next" string, and one of them named a read-only command
|
|
82
|
+
* as the fix for a write problem (C-05). If you need a command for the
|
|
83
|
+
* next-action slot, call this — do not write another one.
|
|
84
|
+
*/
|
|
85
|
+
declare function nextAction(steps: IntegrationStep[], status: IntegrationStatus): NextAction | null;
|
|
86
|
+
declare function computeIntegrationState(facts: IntegrationFacts): IntegrationState;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { INSTALL_COMMAND, INTEGRATION_STEPS, INTEGRATION_STEP_LABELS, IntegrationFacts, IntegrationState, IntegrationStatus, IntegrationStep, NextAction, RunFact, STEP_PREREQUISITES, StepId, StepProblem, StepState, computeIntegrationState, deriveStatus, formatAge, nextAction, resolveSteps, stepIndex };
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
//#region src/integration-state.ts
|
|
2
|
+
const INTEGRATION_STEPS = [
|
|
3
|
+
"credential",
|
|
4
|
+
"application",
|
|
5
|
+
"schema",
|
|
6
|
+
"mapping",
|
|
7
|
+
"trigger",
|
|
8
|
+
"sync",
|
|
9
|
+
"widget"
|
|
10
|
+
];
|
|
11
|
+
const INTEGRATION_STEP_LABELS = {
|
|
12
|
+
credential: "Secure credential",
|
|
13
|
+
application: "Application connected",
|
|
14
|
+
schema: "Schema discovered",
|
|
15
|
+
mapping: "Entities mapped",
|
|
16
|
+
trigger: "Clivly can start a sync",
|
|
17
|
+
sync: "First sync observed",
|
|
18
|
+
widget: "Chat widget installed"
|
|
19
|
+
};
|
|
20
|
+
/** Each step's prerequisites. Ordering is enforced by a test, not convention. */
|
|
21
|
+
const STEP_PREREQUISITES = {
|
|
22
|
+
credential: [],
|
|
23
|
+
application: ["credential"],
|
|
24
|
+
schema: ["application"],
|
|
25
|
+
mapping: ["schema"],
|
|
26
|
+
trigger: ["application"],
|
|
27
|
+
sync: ["mapping", "trigger"],
|
|
28
|
+
widget: ["application"]
|
|
29
|
+
};
|
|
30
|
+
function stepIndex(id) {
|
|
31
|
+
return INTEGRATION_STEPS.indexOf(id);
|
|
32
|
+
}
|
|
33
|
+
/** A heartbeat older than this means the app stopped checking in. */
|
|
34
|
+
const HEARTBEAT_STALE_MS = 600 * 1e3;
|
|
35
|
+
function formatAge(ms) {
|
|
36
|
+
if (ms < 6e4) return `${Math.max(1, Math.round(ms / 1e3))}s ago`;
|
|
37
|
+
if (ms < 36e5) return `${Math.round(ms / 6e4)}m ago`;
|
|
38
|
+
return `${Math.round(ms / 36e5)}h ago`;
|
|
39
|
+
}
|
|
40
|
+
function heartbeatAge(facts) {
|
|
41
|
+
return facts.lastHeartbeatAt === null ? null : facts.now - facts.lastHeartbeatAt;
|
|
42
|
+
}
|
|
43
|
+
/** Is the raw condition behind each step met, ignoring prerequisites? */
|
|
44
|
+
function isSatisfied(id, facts) {
|
|
45
|
+
const age = heartbeatAge(facts);
|
|
46
|
+
switch (id) {
|
|
47
|
+
case "credential": return facts.hasCredential;
|
|
48
|
+
case "application": return age !== null && age < HEARTBEAT_STALE_MS;
|
|
49
|
+
case "schema": return facts.discoveredTableCount > 0;
|
|
50
|
+
case "mapping": return facts.mappedEntityCount > 0;
|
|
51
|
+
case "trigger": return facts.triggerConfigured;
|
|
52
|
+
case "sync": return facts.lastRun?.ok === true;
|
|
53
|
+
default: return facts.widgetInstalled;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function evidenceFor(id, facts) {
|
|
57
|
+
const age = heartbeatAge(facts);
|
|
58
|
+
if (id === "application" && age !== null) return `heartbeat ${formatAge(age)}`;
|
|
59
|
+
if (id === "schema" && facts.discoveredTableCount > 0) return `found ${facts.discoveredTableCount} tables`;
|
|
60
|
+
if (id === "mapping" && facts.mappedEntityCount > 0) return `${facts.mappedEntityCount} entity mapped`;
|
|
61
|
+
if (id === "trigger" && facts.triggerConfigured) return "trigger URL and secret set";
|
|
62
|
+
if (id === "sync" && facts.lastRun) return `last run ${formatAge(facts.now - facts.lastRun.at)}`;
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
function problemFor(id, facts) {
|
|
66
|
+
if (id === "sync" && facts.lastRun && !facts.lastRun.ok) return {
|
|
67
|
+
code: "sync_failed",
|
|
68
|
+
message: facts.lastRun.message ?? "The last sync run failed.",
|
|
69
|
+
fix: "Open the run log to see the host response."
|
|
70
|
+
};
|
|
71
|
+
if (id === "application") {
|
|
72
|
+
const age = heartbeatAge(facts);
|
|
73
|
+
if (age !== null && age >= HEARTBEAT_STALE_MS) return {
|
|
74
|
+
code: "heartbeat_stale",
|
|
75
|
+
message: `Your app stopped checking in ${formatAge(age)}.`,
|
|
76
|
+
fix: "Start your app, then run `npx clivly status`."
|
|
77
|
+
};
|
|
78
|
+
if (age === null && facts.everConnected) return {
|
|
79
|
+
code: "heartbeat_stale",
|
|
80
|
+
message: "Your application stopped checking in.",
|
|
81
|
+
fix: "Start your app, then run `npx clivly status`."
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Walk the ladder once. A step is `blocked` when an earlier step it needs has
|
|
88
|
+
* not been satisfied — a prerequisite statement, not a verdict about this step
|
|
89
|
+
* — and only the first unsatisfied step is `active`, so exactly one root cause
|
|
90
|
+
* ever surfaces.
|
|
91
|
+
*/
|
|
92
|
+
function resolveSteps(facts) {
|
|
93
|
+
const satisfied = /* @__PURE__ */ new Set();
|
|
94
|
+
const steps = [];
|
|
95
|
+
let activeTaken = false;
|
|
96
|
+
for (const id of INTEGRATION_STEPS) {
|
|
97
|
+
const unmet = STEP_PREREQUISITES[id].find((need) => !satisfied.has(need));
|
|
98
|
+
const label = INTEGRATION_STEP_LABELS[id];
|
|
99
|
+
if (isSatisfied(id, facts)) {
|
|
100
|
+
satisfied.add(id);
|
|
101
|
+
steps.push({
|
|
102
|
+
id,
|
|
103
|
+
label,
|
|
104
|
+
state: "done",
|
|
105
|
+
blockedBy: null,
|
|
106
|
+
evidence: evidenceFor(id, facts),
|
|
107
|
+
problem: null
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (unmet !== void 0) {
|
|
112
|
+
steps.push({
|
|
113
|
+
id,
|
|
114
|
+
label,
|
|
115
|
+
state: "blocked",
|
|
116
|
+
blockedBy: unmet,
|
|
117
|
+
evidence: null,
|
|
118
|
+
problem: null
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const problem = problemFor(id, facts);
|
|
123
|
+
let state = "active";
|
|
124
|
+
if (problem) state = "failed";
|
|
125
|
+
else if (id === "widget") state = "skipped";
|
|
126
|
+
const claimsAttention = state === "active" || state === "failed";
|
|
127
|
+
steps.push({
|
|
128
|
+
id,
|
|
129
|
+
label,
|
|
130
|
+
state: activeTaken && state === "active" ? "blocked" : state,
|
|
131
|
+
blockedBy: null,
|
|
132
|
+
evidence: evidenceFor(id, facts),
|
|
133
|
+
problem: activeTaken ? null : problem
|
|
134
|
+
});
|
|
135
|
+
if (claimsAttention) activeTaken = true;
|
|
136
|
+
}
|
|
137
|
+
return steps;
|
|
138
|
+
}
|
|
139
|
+
const STEP_STATUS_WHEN_ACTIVE = {
|
|
140
|
+
credential: "not_configured",
|
|
141
|
+
application: "awaiting_connection",
|
|
142
|
+
schema: "connected",
|
|
143
|
+
mapping: "mapping_required",
|
|
144
|
+
trigger: "trigger_required",
|
|
145
|
+
sync: "ready_to_sync",
|
|
146
|
+
widget: "healthy"
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Collapse the ladder to the single status the surfaces switch on. Live
|
|
150
|
+
* conditions — a run in flight, a failure, a heartbeat gone quiet — win over
|
|
151
|
+
* ladder position, because they describe the integration as it is now rather
|
|
152
|
+
* than how far setup once got.
|
|
153
|
+
*/
|
|
154
|
+
function deriveStatus(steps, facts) {
|
|
155
|
+
if (facts.syncInFlight) return "syncing";
|
|
156
|
+
if (steps.find((step) => step.id === "application")?.problem?.code === "heartbeat_stale") return "disconnected";
|
|
157
|
+
if (steps.some((step) => step.state === "failed")) return "error";
|
|
158
|
+
const blocking = steps.find((step) => step.state === "active" || step.state === "blocked");
|
|
159
|
+
if (blocking) return STEP_STATUS_WHEN_ACTIVE[blocking.id];
|
|
160
|
+
return facts.warnings.length > 0 ? "warning" : "healthy";
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The command that installs Clivly into a host app. Exported because the connect
|
|
164
|
+
* dialog prints it too, and it must be the same string as the one the ladder
|
|
165
|
+
* recommends — that pairing is what C-05 broke.
|
|
166
|
+
*/
|
|
167
|
+
const INSTALL_COMMAND = "npx clivly init";
|
|
168
|
+
const NEXT_BY_STEP = {
|
|
169
|
+
credential: {
|
|
170
|
+
label: "Create a credential",
|
|
171
|
+
command: null,
|
|
172
|
+
envFile: null
|
|
173
|
+
},
|
|
174
|
+
application: {
|
|
175
|
+
label: "Install Clivly in your app",
|
|
176
|
+
command: INSTALL_COMMAND,
|
|
177
|
+
envFile: ".env.local"
|
|
178
|
+
},
|
|
179
|
+
schema: {
|
|
180
|
+
label: "Check what the SDK reported",
|
|
181
|
+
command: "npx clivly status",
|
|
182
|
+
envFile: null
|
|
183
|
+
},
|
|
184
|
+
mapping: {
|
|
185
|
+
label: "Choose the table holding your people",
|
|
186
|
+
command: null,
|
|
187
|
+
envFile: null
|
|
188
|
+
},
|
|
189
|
+
trigger: {
|
|
190
|
+
label: "Let Clivly start a sync",
|
|
191
|
+
command: null,
|
|
192
|
+
envFile: null
|
|
193
|
+
},
|
|
194
|
+
sync: {
|
|
195
|
+
label: "Run the first sync",
|
|
196
|
+
command: "npx clivly sync",
|
|
197
|
+
envFile: null
|
|
198
|
+
},
|
|
199
|
+
widget: {
|
|
200
|
+
label: "Install the chat widget",
|
|
201
|
+
command: null,
|
|
202
|
+
envFile: null
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* THE one producer of `next.command`. Six surfaces previously each invented
|
|
207
|
+
* their own "run this next" string, and one of them named a read-only command
|
|
208
|
+
* as the fix for a write problem (C-05). If you need a command for the
|
|
209
|
+
* next-action slot, call this — do not write another one.
|
|
210
|
+
*/
|
|
211
|
+
function nextAction(steps, status) {
|
|
212
|
+
if (status === "healthy" || status === "syncing") return null;
|
|
213
|
+
const blocking = steps.find((step) => step.state === "failed") ?? steps.find((step) => step.state === "active");
|
|
214
|
+
if (!blocking) return null;
|
|
215
|
+
if (blocking.problem?.code === "heartbeat_stale") return {
|
|
216
|
+
label: "Reconnect your app",
|
|
217
|
+
command: "npx clivly status",
|
|
218
|
+
envFile: null
|
|
219
|
+
};
|
|
220
|
+
return NEXT_BY_STEP[blocking.id];
|
|
221
|
+
}
|
|
222
|
+
function computeIntegrationState(facts) {
|
|
223
|
+
const steps = resolveSteps(facts);
|
|
224
|
+
const status = deriveStatus(steps, facts);
|
|
225
|
+
return {
|
|
226
|
+
status,
|
|
227
|
+
steps,
|
|
228
|
+
next: nextAction(steps, status)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
export { INSTALL_COMMAND, INTEGRATION_STEPS, INTEGRATION_STEP_LABELS, STEP_PREREQUISITES, computeIntegrationState, deriveStatus, formatAge, nextAction, resolveSteps, stepIndex };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { ClivlyEntitiesConfig } from "./entity-config.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/projection.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Projection-backed custom mappings — one custom object derived from a base
|
|
6
|
+
* table plus explicit joins.
|
|
7
|
+
*
|
|
8
|
+
* A projection is deliberately narrow: equality joins only, direct column
|
|
9
|
+
* bindings only, no expressions and no aggregation. Everything a user can
|
|
10
|
+
* express here compiles to a single read-only view whose `id` column is
|
|
11
|
+
* provably unique per row, which is what lets the sync engine consume it
|
|
12
|
+
* unchanged.
|
|
13
|
+
*
|
|
14
|
+
* Refs address tables symbolically: `"base"` for the base table, or a join's
|
|
15
|
+
* `key`. Raw table names are never accepted in a ref, so a projection can
|
|
16
|
+
* never reach a table it did not declare.
|
|
17
|
+
*/
|
|
18
|
+
/** The reserved ref name for the projection's base table. */
|
|
19
|
+
declare const BASE_REF = "base";
|
|
20
|
+
declare const projectionRefSchema: z.ZodObject<{
|
|
21
|
+
table: z.ZodString;
|
|
22
|
+
column: z.ZodString;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
type ProjectionRef = z.infer<typeof projectionRefSchema>;
|
|
25
|
+
declare const projectionJoinSchema: z.ZodObject<{
|
|
26
|
+
key: z.ZodString;
|
|
27
|
+
table: z.ZodString;
|
|
28
|
+
type: z.ZodEnum<{
|
|
29
|
+
inner: "inner";
|
|
30
|
+
left: "left";
|
|
31
|
+
}>;
|
|
32
|
+
on: z.ZodArray<z.ZodObject<{
|
|
33
|
+
left: z.ZodObject<{
|
|
34
|
+
table: z.ZodString;
|
|
35
|
+
column: z.ZodString;
|
|
36
|
+
}, z.core.$strict>;
|
|
37
|
+
op: z.ZodLiteral<"eq">;
|
|
38
|
+
right: z.ZodObject<{
|
|
39
|
+
table: z.ZodString;
|
|
40
|
+
column: z.ZodString;
|
|
41
|
+
}, z.core.$strict>;
|
|
42
|
+
}, z.core.$strict>>;
|
|
43
|
+
}, z.core.$strict>;
|
|
44
|
+
type ProjectionJoin = z.infer<typeof projectionJoinSchema>;
|
|
45
|
+
declare const projectionFieldBindingSchema: z.ZodObject<{
|
|
46
|
+
from: z.ZodObject<{
|
|
47
|
+
table: z.ZodString;
|
|
48
|
+
column: z.ZodString;
|
|
49
|
+
}, z.core.$strict>;
|
|
50
|
+
}, z.core.$strict>;
|
|
51
|
+
type ProjectionFieldBinding = z.infer<typeof projectionFieldBindingSchema>;
|
|
52
|
+
declare const projectionIdentitySchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
53
|
+
kind: z.ZodLiteral<"base_pk">;
|
|
54
|
+
column: z.ZodOptional<z.ZodString>;
|
|
55
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
56
|
+
kind: z.ZodLiteral<"column">;
|
|
57
|
+
from: z.ZodObject<{
|
|
58
|
+
table: z.ZodString;
|
|
59
|
+
column: z.ZodString;
|
|
60
|
+
}, z.core.$strict>;
|
|
61
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
62
|
+
kind: z.ZodLiteral<"composite">;
|
|
63
|
+
parts: z.ZodArray<z.ZodObject<{
|
|
64
|
+
table: z.ZodString;
|
|
65
|
+
column: z.ZodString;
|
|
66
|
+
}, z.core.$strict>>;
|
|
67
|
+
}, z.core.$strict>]>;
|
|
68
|
+
type ProjectionIdentity = z.infer<typeof projectionIdentitySchema>;
|
|
69
|
+
declare const projectionSchema: z.ZodObject<{
|
|
70
|
+
version: z.ZodLiteral<1>;
|
|
71
|
+
kind: z.ZodLiteral<"projection">;
|
|
72
|
+
entityKey: z.ZodString;
|
|
73
|
+
baseTable: z.ZodString;
|
|
74
|
+
joins: z.ZodArray<z.ZodObject<{
|
|
75
|
+
key: z.ZodString;
|
|
76
|
+
table: z.ZodString;
|
|
77
|
+
type: z.ZodEnum<{
|
|
78
|
+
inner: "inner";
|
|
79
|
+
left: "left";
|
|
80
|
+
}>;
|
|
81
|
+
on: z.ZodArray<z.ZodObject<{
|
|
82
|
+
left: z.ZodObject<{
|
|
83
|
+
table: z.ZodString;
|
|
84
|
+
column: z.ZodString;
|
|
85
|
+
}, z.core.$strict>;
|
|
86
|
+
op: z.ZodLiteral<"eq">;
|
|
87
|
+
right: z.ZodObject<{
|
|
88
|
+
table: z.ZodString;
|
|
89
|
+
column: z.ZodString;
|
|
90
|
+
}, z.core.$strict>;
|
|
91
|
+
}, z.core.$strict>>;
|
|
92
|
+
}, z.core.$strict>>;
|
|
93
|
+
fieldBindings: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
94
|
+
from: z.ZodObject<{
|
|
95
|
+
table: z.ZodString;
|
|
96
|
+
column: z.ZodString;
|
|
97
|
+
}, z.core.$strict>;
|
|
98
|
+
}, z.core.$strict>>;
|
|
99
|
+
identity: z.ZodUnion<readonly [z.ZodObject<{
|
|
100
|
+
kind: z.ZodLiteral<"base_pk">;
|
|
101
|
+
column: z.ZodOptional<z.ZodString>;
|
|
102
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
103
|
+
kind: z.ZodLiteral<"column">;
|
|
104
|
+
from: z.ZodObject<{
|
|
105
|
+
table: z.ZodString;
|
|
106
|
+
column: z.ZodString;
|
|
107
|
+
}, z.core.$strict>;
|
|
108
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
109
|
+
kind: z.ZodLiteral<"composite">;
|
|
110
|
+
parts: z.ZodArray<z.ZodObject<{
|
|
111
|
+
table: z.ZodString;
|
|
112
|
+
column: z.ZodString;
|
|
113
|
+
}, z.core.$strict>>;
|
|
114
|
+
}, z.core.$strict>]>;
|
|
115
|
+
}, z.core.$strict>;
|
|
116
|
+
type ProjectionMapping = z.infer<typeof projectionSchema>;
|
|
117
|
+
/**
|
|
118
|
+
* Every column ref an identity depends on. `basePkColumn` is the base table's
|
|
119
|
+
* discovered PK, used only when a `base_pk` identity omits an explicit column.
|
|
120
|
+
*/
|
|
121
|
+
declare function identityRefs(identity: ProjectionIdentity, basePkColumn: string): ProjectionRef[];
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/mappings-config.d.ts
|
|
124
|
+
/**
|
|
125
|
+
* Mappings → Entity Config bridge.
|
|
126
|
+
*
|
|
127
|
+
* `crm_entity_mappings` rows are the single source of truth for how a host
|
|
128
|
+
* schema maps onto Clivly's CRM concepts. This module compiles those rows into
|
|
129
|
+
* a `ClivlyEntitiesConfig` — the contract the view compiler and sync engine
|
|
130
|
+
* consume — so the persisted mapping and the runtime pipeline never diverge.
|
|
131
|
+
*
|
|
132
|
+
* The row model has no explicit entity-key column (it's keyed by
|
|
133
|
+
* `sourceTable + targetConcept + role`), but the config's `relationships`
|
|
134
|
+
* reference other entities *by key*. This builder therefore derives a stable
|
|
135
|
+
* key per row and resolves each relationship's `entity` reference through the
|
|
136
|
+
* same derivation — so a relationship stored as `entity: "<relatedTable>"`
|
|
137
|
+
* (the shape `mappings.suggest` produces) resolves to the right entity.
|
|
138
|
+
*/
|
|
139
|
+
/**
|
|
140
|
+
* A persisted `crm_entity_mappings` row, narrowed to the columns the builder
|
|
141
|
+
* reads. Structurally compatible with the Drizzle row (jsonb columns typed
|
|
142
|
+
* loosely) so the API layer can pass `db.select()` results straight in.
|
|
143
|
+
*/
|
|
144
|
+
interface EntityMappingRow {
|
|
145
|
+
fieldMap: Record<string, string>;
|
|
146
|
+
filter?: Record<string, unknown> | null;
|
|
147
|
+
/**
|
|
148
|
+
* Advanced mapping: a base table plus explicit joins. When set, this — not
|
|
149
|
+
* `fieldMap` — is the authored source of truth, and `sourceTable` merely
|
|
150
|
+
* mirrors `projection.baseTable`.
|
|
151
|
+
*/
|
|
152
|
+
projection?: ProjectionMapping | null;
|
|
153
|
+
relationships?: Record<string, unknown> | null;
|
|
154
|
+
role?: string | null;
|
|
155
|
+
sourceTable: string;
|
|
156
|
+
status?: string | null;
|
|
157
|
+
targetConcept: string;
|
|
158
|
+
targetObjectTypeId?: string | null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Compile persisted mapping rows into a `ClivlyEntitiesConfig`. Only `active`
|
|
162
|
+
* rows with at least one mapped field are included; disabled/suggested rows and
|
|
163
|
+
* unmapped rows are skipped. Returns `{ entities: {} }` when nothing is
|
|
164
|
+
* eligible — a no-op the view compiler and sync engine both tolerate.
|
|
165
|
+
*/
|
|
166
|
+
declare function mappingsToEntitiesConfig(rows: EntityMappingRow[]): ClivlyEntitiesConfig;
|
|
167
|
+
//#endregion
|
|
168
|
+
export { ProjectionIdentity as a, ProjectionRef as c, ProjectionFieldBinding as i, identityRefs as l, mappingsToEntitiesConfig as n, ProjectionJoin as o, BASE_REF as r, ProjectionMapping as s, EntityMappingRow as t, projectionSchema as u };
|