clivly 0.1.0 → 0.2.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 +63 -3
- package/adapter-nLfZpgIG.d.cts +40 -0
- package/adapter-xs04z0KX.d.mts +40 -0
- package/auth-adapter-CBOKDhlc.cjs +11 -0
- package/auth-adapter-FEOR1nx-.d.mts +30 -0
- package/auth-adapter-wqnnEjo3.mjs +6 -0
- package/auth-adapter-wvfGKsOJ.d.cts +30 -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 +926 -0
- package/index.d.cts +56 -0
- package/index.d.mts +56 -0
- package/index.mjs +925 -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 +224 -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-C4POUcvk.d.mts +158 -0
- package/types-LJTBhkcN.d.cts +158 -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 -193
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region ../core/src/mapping-form.ts
|
|
2
|
+
/**
|
|
3
|
+
* Build the filter for a discriminated entity: rows where `column` equals
|
|
4
|
+
* `value` belong to this entity (e.g. `role = "owner"`). Returns `undefined`
|
|
5
|
+
* when either side is blank so the caller can omit the filter entirely.
|
|
6
|
+
*/
|
|
7
|
+
function buildFilterFromDiscriminator(column, value) {
|
|
8
|
+
const col = column?.trim();
|
|
9
|
+
const val = value.trim();
|
|
10
|
+
if (!col || val === "") return;
|
|
11
|
+
return { [col]: { eq: val } };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A stable identity string for a relationship's `via`. Two candidates on the
|
|
15
|
+
* same source table always differ in their join (`fkOn`+column, join-table
|
|
16
|
+
* keys, or raw SQL), so this disambiguates candidates the heuristic gives the
|
|
17
|
+
* same `name` (every contact→company candidate is named "company").
|
|
18
|
+
*/
|
|
19
|
+
function relationshipViaSignature(via) {
|
|
20
|
+
if ("fkOn" in via) return `fk:${via.fkOn}:${via.column}`;
|
|
21
|
+
if ("through" in via) return `through:${via.through}:${via.localKey}:${via.foreignKey}`;
|
|
22
|
+
return `raw:${via.$raw}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Compile the selected relationship candidates into the persisted
|
|
26
|
+
* `RelationshipsMap`. Returns `undefined` for an empty selection so the caller
|
|
27
|
+
* omits the field.
|
|
28
|
+
*/
|
|
29
|
+
function buildRelationships(candidates) {
|
|
30
|
+
if (candidates.length === 0) return;
|
|
31
|
+
const map = {};
|
|
32
|
+
for (const candidate of candidates) {
|
|
33
|
+
let key = candidate.name;
|
|
34
|
+
let suffix = 2;
|
|
35
|
+
while (key in map) {
|
|
36
|
+
key = `${candidate.name}_${suffix}`;
|
|
37
|
+
suffix++;
|
|
38
|
+
}
|
|
39
|
+
map[key] = {
|
|
40
|
+
entity: candidate.entityTable,
|
|
41
|
+
via: candidate.via
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return map;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { buildRelationships as n, relationshipViaSignature as r, buildFilterFromDiscriminator as t };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { _ as RelationshipsMap, d as FilterExpr, g as RelationshipVia } from "./entity-config-GczjD3y8.cjs";
|
|
2
|
+
|
|
3
|
+
//#region ../core/src/mapping-form.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A relationship the mapping UI can offer or has configured, in the shape the
|
|
6
|
+
* heuristic suggester produces. `entityTable` is the *host table name*; the
|
|
7
|
+
* mappings→config compiler resolves it to the canonical entity key, so it is
|
|
8
|
+
* persisted verbatim as the relationship's `entity` reference.
|
|
9
|
+
*/
|
|
10
|
+
interface RelationshipCandidate {
|
|
11
|
+
entityTable: string;
|
|
12
|
+
name: string;
|
|
13
|
+
via: RelationshipVia;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build the filter for a discriminated entity: rows where `column` equals
|
|
17
|
+
* `value` belong to this entity (e.g. `role = "owner"`). Returns `undefined`
|
|
18
|
+
* when either side is blank so the caller can omit the filter entirely.
|
|
19
|
+
*/
|
|
20
|
+
declare function buildFilterFromDiscriminator(column: string | null | undefined, value: string): FilterExpr | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* A stable identity string for a relationship's `via`. Two candidates on the
|
|
23
|
+
* same source table always differ in their join (`fkOn`+column, join-table
|
|
24
|
+
* keys, or raw SQL), so this disambiguates candidates the heuristic gives the
|
|
25
|
+
* same `name` (every contact→company candidate is named "company").
|
|
26
|
+
*/
|
|
27
|
+
declare function relationshipViaSignature(via: RelationshipVia): string;
|
|
28
|
+
/**
|
|
29
|
+
* Compile the selected relationship candidates into the persisted
|
|
30
|
+
* `RelationshipsMap`. Returns `undefined` for an empty selection so the caller
|
|
31
|
+
* omits the field.
|
|
32
|
+
*/
|
|
33
|
+
declare function buildRelationships(candidates: RelationshipCandidate[]): RelationshipsMap | undefined;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { relationshipViaSignature as i, buildFilterFromDiscriminator as n, buildRelationships as r, RelationshipCandidate as t };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region ../core/src/mapping-form.ts
|
|
2
|
+
/**
|
|
3
|
+
* Build the filter for a discriminated entity: rows where `column` equals
|
|
4
|
+
* `value` belong to this entity (e.g. `role = "owner"`). Returns `undefined`
|
|
5
|
+
* when either side is blank so the caller can omit the filter entirely.
|
|
6
|
+
*/
|
|
7
|
+
function buildFilterFromDiscriminator(column, value) {
|
|
8
|
+
const col = column?.trim();
|
|
9
|
+
const val = value.trim();
|
|
10
|
+
if (!col || val === "") return;
|
|
11
|
+
return { [col]: { eq: val } };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A stable identity string for a relationship's `via`. Two candidates on the
|
|
15
|
+
* same source table always differ in their join (`fkOn`+column, join-table
|
|
16
|
+
* keys, or raw SQL), so this disambiguates candidates the heuristic gives the
|
|
17
|
+
* same `name` (every contact→company candidate is named "company").
|
|
18
|
+
*/
|
|
19
|
+
function relationshipViaSignature(via) {
|
|
20
|
+
if ("fkOn" in via) return `fk:${via.fkOn}:${via.column}`;
|
|
21
|
+
if ("through" in via) return `through:${via.through}:${via.localKey}:${via.foreignKey}`;
|
|
22
|
+
return `raw:${via.$raw}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Compile the selected relationship candidates into the persisted
|
|
26
|
+
* `RelationshipsMap`. Returns `undefined` for an empty selection so the caller
|
|
27
|
+
* omits the field.
|
|
28
|
+
*/
|
|
29
|
+
function buildRelationships(candidates) {
|
|
30
|
+
if (candidates.length === 0) return;
|
|
31
|
+
const map = {};
|
|
32
|
+
for (const candidate of candidates) {
|
|
33
|
+
let key = candidate.name;
|
|
34
|
+
let suffix = 2;
|
|
35
|
+
while (key in map) {
|
|
36
|
+
key = `${candidate.name}_${suffix}`;
|
|
37
|
+
suffix++;
|
|
38
|
+
}
|
|
39
|
+
map[key] = {
|
|
40
|
+
entity: candidate.entityTable,
|
|
41
|
+
via: candidate.via
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return map;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
Object.defineProperty(exports, "buildFilterFromDiscriminator", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
get: function() {
|
|
50
|
+
return buildFilterFromDiscriminator;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
Object.defineProperty(exports, "buildRelationships", {
|
|
54
|
+
enumerable: true,
|
|
55
|
+
get: function() {
|
|
56
|
+
return buildRelationships;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
Object.defineProperty(exports, "relationshipViaSignature", {
|
|
60
|
+
enumerable: true,
|
|
61
|
+
get: function() {
|
|
62
|
+
return relationshipViaSignature;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { _ as RelationshipsMap, d as FilterExpr, g as RelationshipVia } from "./entity-config-D1DFyeUp.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../core/src/mapping-form.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A relationship the mapping UI can offer or has configured, in the shape the
|
|
6
|
+
* heuristic suggester produces. `entityTable` is the *host table name*; the
|
|
7
|
+
* mappings→config compiler resolves it to the canonical entity key, so it is
|
|
8
|
+
* persisted verbatim as the relationship's `entity` reference.
|
|
9
|
+
*/
|
|
10
|
+
interface RelationshipCandidate {
|
|
11
|
+
entityTable: string;
|
|
12
|
+
name: string;
|
|
13
|
+
via: RelationshipVia;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build the filter for a discriminated entity: rows where `column` equals
|
|
17
|
+
* `value` belong to this entity (e.g. `role = "owner"`). Returns `undefined`
|
|
18
|
+
* when either side is blank so the caller can omit the filter entirely.
|
|
19
|
+
*/
|
|
20
|
+
declare function buildFilterFromDiscriminator(column: string | null | undefined, value: string): FilterExpr | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* A stable identity string for a relationship's `via`. Two candidates on the
|
|
23
|
+
* same source table always differ in their join (`fkOn`+column, join-table
|
|
24
|
+
* keys, or raw SQL), so this disambiguates candidates the heuristic gives the
|
|
25
|
+
* same `name` (every contact→company candidate is named "company").
|
|
26
|
+
*/
|
|
27
|
+
declare function relationshipViaSignature(via: RelationshipVia): string;
|
|
28
|
+
/**
|
|
29
|
+
* Compile the selected relationship candidates into the persisted
|
|
30
|
+
* `RelationshipsMap`. Returns `undefined` for an empty selection so the caller
|
|
31
|
+
* omits the field.
|
|
32
|
+
*/
|
|
33
|
+
declare function buildRelationships(candidates: RelationshipCandidate[]): RelationshipsMap | undefined;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { relationshipViaSignature as i, buildFilterFromDiscriminator as n, buildRelationships as r, RelationshipCandidate as t };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region ../core/src/mapping-score.d.ts
|
|
2
|
+
type Tier = "High" | "Medium" | "Low";
|
|
3
|
+
interface Scored<T> {
|
|
4
|
+
/** Human-readable chips, e.g. `type text`, `name matches "contacts"`. */
|
|
5
|
+
reasons: string[];
|
|
6
|
+
/** Internal — used for ranking and the apply threshold. Never rendered. */
|
|
7
|
+
score: number;
|
|
8
|
+
tier: Tier;
|
|
9
|
+
value: T;
|
|
10
|
+
}
|
|
11
|
+
/** Tier cut-offs: score >= high → High; >= medium → Medium; else Low. */
|
|
12
|
+
interface Thresholds {
|
|
13
|
+
high: number;
|
|
14
|
+
medium: number;
|
|
15
|
+
}
|
|
16
|
+
declare const CONCEPT_TIERS: Thresholds;
|
|
17
|
+
declare const FIELD_TIERS: Thresholds;
|
|
18
|
+
declare const RELATIONSHIP_TIERS: Thresholds;
|
|
19
|
+
/** Two concept candidates closer than this are treated as ambiguous. */
|
|
20
|
+
declare const AMBIGUITY_MARGIN = 15;
|
|
21
|
+
/** Signal weights. Tuned against fixtures; adjust here, not at call sites. */
|
|
22
|
+
declare const W: {
|
|
23
|
+
readonly nameExact: 50;
|
|
24
|
+
readonly nameSubstring: 25;
|
|
25
|
+
readonly definingColumn: 20;
|
|
26
|
+
readonly hasPrimaryKey: 10;
|
|
27
|
+
readonly hasRows: 10;
|
|
28
|
+
readonly emptyTable: -20;
|
|
29
|
+
readonly fieldNameMatch: 50;
|
|
30
|
+
readonly typeCompatible: 30;
|
|
31
|
+
readonly typeMismatch: -30;
|
|
32
|
+
readonly requiredNotNull: 10;
|
|
33
|
+
readonly fkResolved: 60;
|
|
34
|
+
readonly fkNameHint: 30;
|
|
35
|
+
readonly joinTable: 20;
|
|
36
|
+
};
|
|
37
|
+
declare function tierFor(score: number, t: Thresholds): Tier;
|
|
38
|
+
declare function isTextLike(type: string | undefined): boolean;
|
|
39
|
+
declare function isNumericLike(type: string | undefined): boolean;
|
|
40
|
+
declare function isTimestampLike(type: string | undefined): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* True when the top two candidates are too close to call. Callers must never
|
|
43
|
+
* one-click apply an ambiguous result — the user picks.
|
|
44
|
+
*/
|
|
45
|
+
declare function isAmbiguous(ranked: Array<{
|
|
46
|
+
score: number;
|
|
47
|
+
}>): boolean;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { Scored as a, W as c, isTextLike as d, isTimestampLike as f, RELATIONSHIP_TIERS as i, isAmbiguous as l, CONCEPT_TIERS as n, Thresholds as o, tierFor as p, FIELD_TIERS as r, Tier as s, AMBIGUITY_MARGIN as t, isNumericLike as u };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//#region ../core/src/mapping-score.ts
|
|
2
|
+
const CONCEPT_TIERS = {
|
|
3
|
+
high: 70,
|
|
4
|
+
medium: 40
|
|
5
|
+
};
|
|
6
|
+
const FIELD_TIERS = {
|
|
7
|
+
high: 70,
|
|
8
|
+
medium: 40
|
|
9
|
+
};
|
|
10
|
+
const RELATIONSHIP_TIERS = {
|
|
11
|
+
high: 60,
|
|
12
|
+
medium: 30
|
|
13
|
+
};
|
|
14
|
+
/** Two concept candidates closer than this are treated as ambiguous. */
|
|
15
|
+
const AMBIGUITY_MARGIN = 15;
|
|
16
|
+
/** Signal weights. Tuned against fixtures; adjust here, not at call sites. */
|
|
17
|
+
const W = {
|
|
18
|
+
nameExact: 50,
|
|
19
|
+
nameSubstring: 25,
|
|
20
|
+
definingColumn: 20,
|
|
21
|
+
hasPrimaryKey: 10,
|
|
22
|
+
hasRows: 10,
|
|
23
|
+
emptyTable: -20,
|
|
24
|
+
fieldNameMatch: 50,
|
|
25
|
+
typeCompatible: 30,
|
|
26
|
+
typeMismatch: -30,
|
|
27
|
+
requiredNotNull: 10,
|
|
28
|
+
fkResolved: 60,
|
|
29
|
+
fkNameHint: 30,
|
|
30
|
+
joinTable: 20
|
|
31
|
+
};
|
|
32
|
+
function tierFor(score, t) {
|
|
33
|
+
if (score >= t.high) return "High";
|
|
34
|
+
if (score >= t.medium) return "Medium";
|
|
35
|
+
return "Low";
|
|
36
|
+
}
|
|
37
|
+
const TEXT_TYPES = [
|
|
38
|
+
"text",
|
|
39
|
+
"varchar",
|
|
40
|
+
"char",
|
|
41
|
+
"citext",
|
|
42
|
+
"uuid",
|
|
43
|
+
"string"
|
|
44
|
+
];
|
|
45
|
+
const NUMERIC_TYPES = [
|
|
46
|
+
"numeric",
|
|
47
|
+
"decimal",
|
|
48
|
+
"int",
|
|
49
|
+
"integer",
|
|
50
|
+
"bigint",
|
|
51
|
+
"smallint",
|
|
52
|
+
"float",
|
|
53
|
+
"double",
|
|
54
|
+
"real",
|
|
55
|
+
"money"
|
|
56
|
+
];
|
|
57
|
+
const TIMESTAMP_TYPES = [
|
|
58
|
+
"timestamp",
|
|
59
|
+
"timestamptz",
|
|
60
|
+
"date",
|
|
61
|
+
"datetime"
|
|
62
|
+
];
|
|
63
|
+
function matchesType(type, list) {
|
|
64
|
+
if (!type) return false;
|
|
65
|
+
const normalized = type.toLowerCase();
|
|
66
|
+
return list.some((t) => normalized.startsWith(t));
|
|
67
|
+
}
|
|
68
|
+
function isTextLike(type) {
|
|
69
|
+
return matchesType(type, TEXT_TYPES);
|
|
70
|
+
}
|
|
71
|
+
function isNumericLike(type) {
|
|
72
|
+
return matchesType(type, NUMERIC_TYPES);
|
|
73
|
+
}
|
|
74
|
+
function isTimestampLike(type) {
|
|
75
|
+
return matchesType(type, TIMESTAMP_TYPES);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* True when the top two candidates are too close to call. Callers must never
|
|
79
|
+
* one-click apply an ambiguous result — the user picks.
|
|
80
|
+
*/
|
|
81
|
+
function isAmbiguous(ranked) {
|
|
82
|
+
const [first, second] = ranked;
|
|
83
|
+
if (!(first && second)) return false;
|
|
84
|
+
return first.score - second.score < 15;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { W as a, isTextLike as c, RELATIONSHIP_TIERS as i, isTimestampLike as l, CONCEPT_TIERS as n, isAmbiguous as o, FIELD_TIERS as r, isNumericLike as s, AMBIGUITY_MARGIN as t, tierFor as u };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region ../core/src/mapping-score.d.ts
|
|
2
|
+
type Tier = "High" | "Medium" | "Low";
|
|
3
|
+
interface Scored<T> {
|
|
4
|
+
/** Human-readable chips, e.g. `type text`, `name matches "contacts"`. */
|
|
5
|
+
reasons: string[];
|
|
6
|
+
/** Internal — used for ranking and the apply threshold. Never rendered. */
|
|
7
|
+
score: number;
|
|
8
|
+
tier: Tier;
|
|
9
|
+
value: T;
|
|
10
|
+
}
|
|
11
|
+
/** Tier cut-offs: score >= high → High; >= medium → Medium; else Low. */
|
|
12
|
+
interface Thresholds {
|
|
13
|
+
high: number;
|
|
14
|
+
medium: number;
|
|
15
|
+
}
|
|
16
|
+
declare const CONCEPT_TIERS: Thresholds;
|
|
17
|
+
declare const FIELD_TIERS: Thresholds;
|
|
18
|
+
declare const RELATIONSHIP_TIERS: Thresholds;
|
|
19
|
+
/** Two concept candidates closer than this are treated as ambiguous. */
|
|
20
|
+
declare const AMBIGUITY_MARGIN = 15;
|
|
21
|
+
/** Signal weights. Tuned against fixtures; adjust here, not at call sites. */
|
|
22
|
+
declare const W: {
|
|
23
|
+
readonly nameExact: 50;
|
|
24
|
+
readonly nameSubstring: 25;
|
|
25
|
+
readonly definingColumn: 20;
|
|
26
|
+
readonly hasPrimaryKey: 10;
|
|
27
|
+
readonly hasRows: 10;
|
|
28
|
+
readonly emptyTable: -20;
|
|
29
|
+
readonly fieldNameMatch: 50;
|
|
30
|
+
readonly typeCompatible: 30;
|
|
31
|
+
readonly typeMismatch: -30;
|
|
32
|
+
readonly requiredNotNull: 10;
|
|
33
|
+
readonly fkResolved: 60;
|
|
34
|
+
readonly fkNameHint: 30;
|
|
35
|
+
readonly joinTable: 20;
|
|
36
|
+
};
|
|
37
|
+
declare function tierFor(score: number, t: Thresholds): Tier;
|
|
38
|
+
declare function isTextLike(type: string | undefined): boolean;
|
|
39
|
+
declare function isNumericLike(type: string | undefined): boolean;
|
|
40
|
+
declare function isTimestampLike(type: string | undefined): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* True when the top two candidates are too close to call. Callers must never
|
|
43
|
+
* one-click apply an ambiguous result — the user picks.
|
|
44
|
+
*/
|
|
45
|
+
declare function isAmbiguous(ranked: Array<{
|
|
46
|
+
score: number;
|
|
47
|
+
}>): boolean;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { Scored as a, W as c, isTextLike as d, isTimestampLike as f, RELATIONSHIP_TIERS as i, isAmbiguous as l, CONCEPT_TIERS as n, Thresholds as o, tierFor as p, FIELD_TIERS as r, Tier as s, AMBIGUITY_MARGIN as t, isNumericLike as u };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
//#region ../core/src/mapping-score.ts
|
|
2
|
+
const CONCEPT_TIERS = {
|
|
3
|
+
high: 70,
|
|
4
|
+
medium: 40
|
|
5
|
+
};
|
|
6
|
+
const FIELD_TIERS = {
|
|
7
|
+
high: 70,
|
|
8
|
+
medium: 40
|
|
9
|
+
};
|
|
10
|
+
const RELATIONSHIP_TIERS = {
|
|
11
|
+
high: 60,
|
|
12
|
+
medium: 30
|
|
13
|
+
};
|
|
14
|
+
/** Two concept candidates closer than this are treated as ambiguous. */
|
|
15
|
+
const AMBIGUITY_MARGIN = 15;
|
|
16
|
+
/** Signal weights. Tuned against fixtures; adjust here, not at call sites. */
|
|
17
|
+
const W = {
|
|
18
|
+
nameExact: 50,
|
|
19
|
+
nameSubstring: 25,
|
|
20
|
+
definingColumn: 20,
|
|
21
|
+
hasPrimaryKey: 10,
|
|
22
|
+
hasRows: 10,
|
|
23
|
+
emptyTable: -20,
|
|
24
|
+
fieldNameMatch: 50,
|
|
25
|
+
typeCompatible: 30,
|
|
26
|
+
typeMismatch: -30,
|
|
27
|
+
requiredNotNull: 10,
|
|
28
|
+
fkResolved: 60,
|
|
29
|
+
fkNameHint: 30,
|
|
30
|
+
joinTable: 20
|
|
31
|
+
};
|
|
32
|
+
function tierFor(score, t) {
|
|
33
|
+
if (score >= t.high) return "High";
|
|
34
|
+
if (score >= t.medium) return "Medium";
|
|
35
|
+
return "Low";
|
|
36
|
+
}
|
|
37
|
+
const TEXT_TYPES = [
|
|
38
|
+
"text",
|
|
39
|
+
"varchar",
|
|
40
|
+
"char",
|
|
41
|
+
"citext",
|
|
42
|
+
"uuid",
|
|
43
|
+
"string"
|
|
44
|
+
];
|
|
45
|
+
const NUMERIC_TYPES = [
|
|
46
|
+
"numeric",
|
|
47
|
+
"decimal",
|
|
48
|
+
"int",
|
|
49
|
+
"integer",
|
|
50
|
+
"bigint",
|
|
51
|
+
"smallint",
|
|
52
|
+
"float",
|
|
53
|
+
"double",
|
|
54
|
+
"real",
|
|
55
|
+
"money"
|
|
56
|
+
];
|
|
57
|
+
const TIMESTAMP_TYPES = [
|
|
58
|
+
"timestamp",
|
|
59
|
+
"timestamptz",
|
|
60
|
+
"date",
|
|
61
|
+
"datetime"
|
|
62
|
+
];
|
|
63
|
+
function matchesType(type, list) {
|
|
64
|
+
if (!type) return false;
|
|
65
|
+
const normalized = type.toLowerCase();
|
|
66
|
+
return list.some((t) => normalized.startsWith(t));
|
|
67
|
+
}
|
|
68
|
+
function isTextLike(type) {
|
|
69
|
+
return matchesType(type, TEXT_TYPES);
|
|
70
|
+
}
|
|
71
|
+
function isNumericLike(type) {
|
|
72
|
+
return matchesType(type, NUMERIC_TYPES);
|
|
73
|
+
}
|
|
74
|
+
function isTimestampLike(type) {
|
|
75
|
+
return matchesType(type, TIMESTAMP_TYPES);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* True when the top two candidates are too close to call. Callers must never
|
|
79
|
+
* one-click apply an ambiguous result — the user picks.
|
|
80
|
+
*/
|
|
81
|
+
function isAmbiguous(ranked) {
|
|
82
|
+
const [first, second] = ranked;
|
|
83
|
+
if (!(first && second)) return false;
|
|
84
|
+
return first.score - second.score < 15;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
Object.defineProperty(exports, "AMBIGUITY_MARGIN", {
|
|
88
|
+
enumerable: true,
|
|
89
|
+
get: function() {
|
|
90
|
+
return AMBIGUITY_MARGIN;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
Object.defineProperty(exports, "CONCEPT_TIERS", {
|
|
94
|
+
enumerable: true,
|
|
95
|
+
get: function() {
|
|
96
|
+
return CONCEPT_TIERS;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
Object.defineProperty(exports, "FIELD_TIERS", {
|
|
100
|
+
enumerable: true,
|
|
101
|
+
get: function() {
|
|
102
|
+
return FIELD_TIERS;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
Object.defineProperty(exports, "RELATIONSHIP_TIERS", {
|
|
106
|
+
enumerable: true,
|
|
107
|
+
get: function() {
|
|
108
|
+
return RELATIONSHIP_TIERS;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
Object.defineProperty(exports, "W", {
|
|
112
|
+
enumerable: true,
|
|
113
|
+
get: function() {
|
|
114
|
+
return W;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
Object.defineProperty(exports, "isAmbiguous", {
|
|
118
|
+
enumerable: true,
|
|
119
|
+
get: function() {
|
|
120
|
+
return isAmbiguous;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
Object.defineProperty(exports, "isNumericLike", {
|
|
124
|
+
enumerable: true,
|
|
125
|
+
get: function() {
|
|
126
|
+
return isNumericLike;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
Object.defineProperty(exports, "isTextLike", {
|
|
130
|
+
enumerable: true,
|
|
131
|
+
get: function() {
|
|
132
|
+
return isTextLike;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
Object.defineProperty(exports, "isTimestampLike", {
|
|
136
|
+
enumerable: true,
|
|
137
|
+
get: function() {
|
|
138
|
+
return isTimestampLike;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
Object.defineProperty(exports, "tierFor", {
|
|
142
|
+
enumerable: true,
|
|
143
|
+
get: function() {
|
|
144
|
+
return tierFor;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { t as CANONICAL_FIELDS } from "./entity-config-DoR0khce.mjs";
|
|
2
|
+
//#region ../core/src/mappings-config.ts
|
|
3
|
+
const RESERVED_FIELD = "id";
|
|
4
|
+
/**
|
|
5
|
+
* Restrict a persisted field map to what the mirror can actually store: drop
|
|
6
|
+
* the reserved `id` key (for every concept) and, for materialized concepts
|
|
7
|
+
* (contact/company), any key that isn't canonical. This makes the pipeline
|
|
8
|
+
* resilient to legacy or mis-suggested field maps — a stray `id` no longer
|
|
9
|
+
* blocks the whole sync.
|
|
10
|
+
*/
|
|
11
|
+
function canonicalFields(concept, fieldMap) {
|
|
12
|
+
const allowed = CANONICAL_FIELDS[concept];
|
|
13
|
+
const result = {};
|
|
14
|
+
for (const [field, column] of Object.entries(fieldMap)) {
|
|
15
|
+
if (field === RESERVED_FIELD) continue;
|
|
16
|
+
if (allowed && !allowed.includes(field)) continue;
|
|
17
|
+
result[field] = column;
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
const DEFAULT_ROLE = "primary";
|
|
22
|
+
const ACTIVE_STATUS = "active";
|
|
23
|
+
function isEligible(row) {
|
|
24
|
+
return (row.status ?? ACTIVE_STATUS) === ACTIVE_STATUS && Object.keys(row.fieldMap ?? {}).length > 0;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Preferred key for a row: the bare source table when it backs a single entity,
|
|
28
|
+
* else role-suffixed so multiple roles on one table stay distinct and stable
|
|
29
|
+
* (the primary role keeps the bare name). Uniqueness is still enforced by the
|
|
30
|
+
* caller in case two rows land on the same preferred key.
|
|
31
|
+
*/
|
|
32
|
+
function preferredKey(row, tableCounts) {
|
|
33
|
+
const role = row.role ?? DEFAULT_ROLE;
|
|
34
|
+
if ((tableCounts.get(row.sourceTable) ?? 0) <= 1) return row.sourceTable;
|
|
35
|
+
return role === DEFAULT_ROLE ? row.sourceTable : `${row.sourceTable}__${role}`;
|
|
36
|
+
}
|
|
37
|
+
function uniquify(base, used) {
|
|
38
|
+
if (!used.has(base)) {
|
|
39
|
+
used.add(base);
|
|
40
|
+
return base;
|
|
41
|
+
}
|
|
42
|
+
let suffix = 2;
|
|
43
|
+
while (used.has(`${base}__${suffix}`)) suffix++;
|
|
44
|
+
const key = `${base}__${suffix}`;
|
|
45
|
+
used.add(key);
|
|
46
|
+
return key;
|
|
47
|
+
}
|
|
48
|
+
function buildEntity(row, refIndex) {
|
|
49
|
+
const entity = {
|
|
50
|
+
concept: row.targetConcept,
|
|
51
|
+
source: row.sourceTable,
|
|
52
|
+
role: row.role ?? DEFAULT_ROLE,
|
|
53
|
+
fields: canonicalFields(row.targetConcept, row.fieldMap)
|
|
54
|
+
};
|
|
55
|
+
if (row.targetConcept === "custom" && row.targetObjectTypeId) entity.targetObjectTypeId = row.targetObjectTypeId;
|
|
56
|
+
if (row.filter && Object.keys(row.filter).length > 0) entity.filter = row.filter;
|
|
57
|
+
if (row.relationships && Object.keys(row.relationships).length > 0) {
|
|
58
|
+
const relationships = {};
|
|
59
|
+
for (const [name, spec] of Object.entries(row.relationships)) {
|
|
60
|
+
const rel = spec;
|
|
61
|
+
relationships[name] = {
|
|
62
|
+
entity: refIndex.get(rel.entity) ?? rel.entity,
|
|
63
|
+
via: rel.via
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
entity.relationships = relationships;
|
|
67
|
+
}
|
|
68
|
+
return entity;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Compile persisted mapping rows into a `ClivlyEntitiesConfig`. Only `active`
|
|
72
|
+
* rows with at least one mapped field are included; disabled/suggested rows and
|
|
73
|
+
* unmapped rows are skipped. Returns `{ entities: {} }` when nothing is
|
|
74
|
+
* eligible — a no-op the view compiler and sync engine both tolerate.
|
|
75
|
+
*/
|
|
76
|
+
function mappingsToEntitiesConfig(rows) {
|
|
77
|
+
const eligible = rows.filter(isEligible);
|
|
78
|
+
const tableCounts = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const row of eligible) tableCounts.set(row.sourceTable, (tableCounts.get(row.sourceTable) ?? 0) + 1);
|
|
80
|
+
const used = /* @__PURE__ */ new Set();
|
|
81
|
+
const tableAnchor = /* @__PURE__ */ new Map();
|
|
82
|
+
const assigned = [];
|
|
83
|
+
for (const row of eligible) {
|
|
84
|
+
const key = uniquify(preferredKey(row, tableCounts), used);
|
|
85
|
+
assigned.push({
|
|
86
|
+
row,
|
|
87
|
+
key
|
|
88
|
+
});
|
|
89
|
+
if ((row.role ?? DEFAULT_ROLE) === DEFAULT_ROLE || !tableAnchor.has(row.sourceTable)) tableAnchor.set(row.sourceTable, key);
|
|
90
|
+
}
|
|
91
|
+
const refIndex = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const { key } of assigned) refIndex.set(key, key);
|
|
93
|
+
for (const [table, key] of tableAnchor) if (!refIndex.has(table)) refIndex.set(table, key);
|
|
94
|
+
const entities = {};
|
|
95
|
+
for (const { row, key } of assigned) entities[key] = buildEntity(row, refIndex);
|
|
96
|
+
return { entities };
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { mappingsToEntitiesConfig as t };
|