@clivly/core 0.2.0 → 0.3.0-next.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/dist/adapter.d.cts +13 -3
- package/dist/adapter.d.mts +13 -3
- package/dist/entity-config.cjs +39 -3
- package/dist/entity-config.d.cts +29 -5
- package/dist/entity-config.d.mts +29 -5
- package/dist/entity-config.mjs +38 -4
- package/dist/entity-heuristics.cjs +160 -62
- package/dist/entity-heuristics.d.cts +38 -13
- package/dist/entity-heuristics.d.mts +38 -13
- package/dist/entity-heuristics.mjs +156 -60
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/mapping-score.cjs +97 -0
- package/dist/mapping-score.d.cts +49 -0
- package/dist/mapping-score.d.mts +49 -0
- package/dist/mapping-score.mjs +87 -0
- package/dist/mappings-config.cjs +1 -0
- package/dist/mappings-config.d.cts +1 -0
- package/dist/mappings-config.d.mts +1 -0
- package/dist/mappings-config.mjs +1 -0
- package/dist/sync-engine.cjs +54 -4
- package/dist/sync-engine.d.cts +40 -5
- package/dist/sync-engine.d.mts +40 -5
- package/dist/sync-engine.mjs +53 -5
- package/dist/types.d.cts +4 -1
- package/dist/types.d.mts +4 -1
- package/dist/view-compiler.d.cts +7 -0
- package/dist/view-compiler.d.mts +7 -0
- package/package.json +12 -2
|
@@ -1,13 +1,35 @@
|
|
|
1
|
+
import { Scored } from "./mapping-score.cjs";
|
|
2
|
+
|
|
1
3
|
//#region src/entity-heuristics.d.ts
|
|
2
4
|
declare const TARGET_CONCEPTS: readonly ["contact", "company", "deal", "activity", "conversation", "note"];
|
|
3
5
|
type TargetConcept = (typeof TARGET_CONCEPTS)[number];
|
|
4
6
|
declare function suggestCursorField(columnKeys: string[]): string | null;
|
|
5
|
-
declare function suggestConcept(tableName: string): TargetConcept | null;
|
|
6
7
|
declare function matchesConcept(tableName: string, concept: TargetConcept): {
|
|
7
8
|
exact: boolean;
|
|
8
9
|
} | null;
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
interface DiscoveredColumnMeta {
|
|
11
|
+
isForeignKey?: boolean;
|
|
12
|
+
isPrimaryKey?: boolean;
|
|
13
|
+
name: string;
|
|
14
|
+
nullable?: boolean;
|
|
15
|
+
references?: {
|
|
16
|
+
table: string;
|
|
17
|
+
column: string;
|
|
18
|
+
};
|
|
19
|
+
type?: string;
|
|
20
|
+
}
|
|
21
|
+
interface ScoredTable {
|
|
22
|
+
columns: string[];
|
|
23
|
+
columnsMeta?: DiscoveredColumnMeta[];
|
|
24
|
+
name: string;
|
|
25
|
+
rowCount?: number;
|
|
26
|
+
}
|
|
27
|
+
declare function scoreConcepts(table: ScoredTable): Scored<TargetConcept>[];
|
|
28
|
+
declare function topConcept(table: ScoredTable): {
|
|
29
|
+
best: Scored<TargetConcept>;
|
|
30
|
+
ambiguous: boolean;
|
|
31
|
+
} | null;
|
|
32
|
+
declare function requiredFieldsFor(concept: TargetConcept | "custom"): string[];
|
|
11
33
|
declare function validateMapping(requiredFields: string[], fieldMap: Record<string, string>): {
|
|
12
34
|
valid: boolean;
|
|
13
35
|
missing: string[];
|
|
@@ -28,16 +50,19 @@ interface RelationshipSuggestion {
|
|
|
28
50
|
name: string;
|
|
29
51
|
via: SuggestedVia;
|
|
30
52
|
}
|
|
31
|
-
interface HeuristicTable {
|
|
32
|
-
columns: string[];
|
|
33
|
-
name: string;
|
|
34
|
-
}
|
|
35
53
|
/**
|
|
36
|
-
* Suggest contact→company relationships
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
54
|
+
* Suggest contact→company relationships. When the source table carries FK
|
|
55
|
+
* metadata (`references`), the target table is a *fact*, not a name guess —
|
|
56
|
+
* that scores highest. Without metadata (v1 hosts) we fall back to the legacy
|
|
57
|
+
* FK-name hints at a lower tier.
|
|
40
58
|
*/
|
|
41
|
-
declare function
|
|
59
|
+
declare function scoreRelationships(source: ScoredTable, allTables: ScoredTable[]): Scored<RelationshipSuggestion>[];
|
|
60
|
+
interface FieldSuggestion {
|
|
61
|
+
column: string;
|
|
62
|
+
field: string;
|
|
63
|
+
required: boolean;
|
|
64
|
+
}
|
|
65
|
+
declare function scoreFieldMap(concept: TargetConcept, table: ScoredTable): Scored<FieldSuggestion>[];
|
|
66
|
+
declare function toFieldMap(scored: Scored<FieldSuggestion>[]): Record<string, string>;
|
|
42
67
|
//#endregion
|
|
43
|
-
export { RelationshipSuggestion, SuggestedVia, TARGET_CONCEPTS, TargetConcept, matchesConcept, requiredFieldsFor,
|
|
68
|
+
export { DiscoveredColumnMeta, FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
@@ -1,13 +1,35 @@
|
|
|
1
|
+
import { Scored } from "./mapping-score.mjs";
|
|
2
|
+
|
|
1
3
|
//#region src/entity-heuristics.d.ts
|
|
2
4
|
declare const TARGET_CONCEPTS: readonly ["contact", "company", "deal", "activity", "conversation", "note"];
|
|
3
5
|
type TargetConcept = (typeof TARGET_CONCEPTS)[number];
|
|
4
6
|
declare function suggestCursorField(columnKeys: string[]): string | null;
|
|
5
|
-
declare function suggestConcept(tableName: string): TargetConcept | null;
|
|
6
7
|
declare function matchesConcept(tableName: string, concept: TargetConcept): {
|
|
7
8
|
exact: boolean;
|
|
8
9
|
} | null;
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
interface DiscoveredColumnMeta {
|
|
11
|
+
isForeignKey?: boolean;
|
|
12
|
+
isPrimaryKey?: boolean;
|
|
13
|
+
name: string;
|
|
14
|
+
nullable?: boolean;
|
|
15
|
+
references?: {
|
|
16
|
+
table: string;
|
|
17
|
+
column: string;
|
|
18
|
+
};
|
|
19
|
+
type?: string;
|
|
20
|
+
}
|
|
21
|
+
interface ScoredTable {
|
|
22
|
+
columns: string[];
|
|
23
|
+
columnsMeta?: DiscoveredColumnMeta[];
|
|
24
|
+
name: string;
|
|
25
|
+
rowCount?: number;
|
|
26
|
+
}
|
|
27
|
+
declare function scoreConcepts(table: ScoredTable): Scored<TargetConcept>[];
|
|
28
|
+
declare function topConcept(table: ScoredTable): {
|
|
29
|
+
best: Scored<TargetConcept>;
|
|
30
|
+
ambiguous: boolean;
|
|
31
|
+
} | null;
|
|
32
|
+
declare function requiredFieldsFor(concept: TargetConcept | "custom"): string[];
|
|
11
33
|
declare function validateMapping(requiredFields: string[], fieldMap: Record<string, string>): {
|
|
12
34
|
valid: boolean;
|
|
13
35
|
missing: string[];
|
|
@@ -28,16 +50,19 @@ interface RelationshipSuggestion {
|
|
|
28
50
|
name: string;
|
|
29
51
|
via: SuggestedVia;
|
|
30
52
|
}
|
|
31
|
-
interface HeuristicTable {
|
|
32
|
-
columns: string[];
|
|
33
|
-
name: string;
|
|
34
|
-
}
|
|
35
53
|
/**
|
|
36
|
-
* Suggest contact→company relationships
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
54
|
+
* Suggest contact→company relationships. When the source table carries FK
|
|
55
|
+
* metadata (`references`), the target table is a *fact*, not a name guess —
|
|
56
|
+
* that scores highest. Without metadata (v1 hosts) we fall back to the legacy
|
|
57
|
+
* FK-name hints at a lower tier.
|
|
40
58
|
*/
|
|
41
|
-
declare function
|
|
59
|
+
declare function scoreRelationships(source: ScoredTable, allTables: ScoredTable[]): Scored<RelationshipSuggestion>[];
|
|
60
|
+
interface FieldSuggestion {
|
|
61
|
+
column: string;
|
|
62
|
+
field: string;
|
|
63
|
+
required: boolean;
|
|
64
|
+
}
|
|
65
|
+
declare function scoreFieldMap(concept: TargetConcept, table: ScoredTable): Scored<FieldSuggestion>[];
|
|
66
|
+
declare function toFieldMap(scored: Scored<FieldSuggestion>[]): Record<string, string>;
|
|
42
67
|
//#endregion
|
|
43
|
-
export { RelationshipSuggestion, SuggestedVia, TARGET_CONCEPTS, TargetConcept, matchesConcept, requiredFieldsFor,
|
|
68
|
+
export { DiscoveredColumnMeta, FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { MATERIALIZING_CONCEPTS } from "./entity-config.mjs";
|
|
2
|
+
import { CONCEPT_TIERS, FIELD_TIERS, RELATIONSHIP_TIERS, W, isAmbiguous, isNumericLike, isTextLike, isTimestampLike, tierFor } from "./mapping-score.mjs";
|
|
1
3
|
//#region src/entity-heuristics.ts
|
|
2
4
|
const TARGET_CONCEPTS = [
|
|
3
5
|
"contact",
|
|
@@ -115,7 +117,7 @@ const CONCEPT_FIELDS = {
|
|
|
115
117
|
{
|
|
116
118
|
field: "name",
|
|
117
119
|
candidates: ["name", "title"],
|
|
118
|
-
required:
|
|
120
|
+
required: true
|
|
119
121
|
},
|
|
120
122
|
{
|
|
121
123
|
field: "value",
|
|
@@ -207,12 +209,7 @@ function suggestCursorField(columnKeys) {
|
|
|
207
209
|
}
|
|
208
210
|
return null;
|
|
209
211
|
}
|
|
210
|
-
|
|
211
|
-
const normalized = tableName.toLowerCase();
|
|
212
|
-
for (const { concept, names } of TABLE_PATTERNS) if (names.includes(normalized)) return concept;
|
|
213
|
-
for (const { concept, names } of TABLE_PATTERNS) if (names.some((name) => normalized.includes(name))) return concept;
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
212
|
+
const MATERIALIZING = new Set(MATERIALIZING_CONCEPTS);
|
|
216
213
|
function matchesConcept(tableName, concept) {
|
|
217
214
|
const normalized = tableName.toLowerCase();
|
|
218
215
|
const entry = TABLE_PATTERNS.find((p) => p.concept === concept);
|
|
@@ -221,19 +218,64 @@ function matchesConcept(tableName, concept) {
|
|
|
221
218
|
if (entry.names.some((name) => normalized.includes(name))) return { exact: false };
|
|
222
219
|
return null;
|
|
223
220
|
}
|
|
224
|
-
function
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
221
|
+
function metaFor(table, column) {
|
|
222
|
+
return table.columnsMeta?.find((c) => c.name === column);
|
|
223
|
+
}
|
|
224
|
+
function hasDefiningColumn(concept, columns) {
|
|
225
|
+
const lower = new Set(columns.map((c) => c.toLowerCase()));
|
|
226
|
+
const required = CONCEPT_FIELDS[concept].filter((f) => f.required);
|
|
227
|
+
return required.length > 0 && required.every((f) => f.candidates.some((c) => lower.has(c)));
|
|
228
|
+
}
|
|
229
|
+
function scoreConcepts(table) {
|
|
230
|
+
const out = [];
|
|
231
|
+
for (const concept of TARGET_CONCEPTS) {
|
|
232
|
+
if (!MATERIALIZING.has(concept)) continue;
|
|
233
|
+
const nameMatch = matchesConcept(table.name, concept);
|
|
234
|
+
if (!nameMatch) continue;
|
|
235
|
+
let score = 0;
|
|
236
|
+
const reasons = [];
|
|
237
|
+
if (nameMatch.exact) {
|
|
238
|
+
score += W.nameExact;
|
|
239
|
+
reasons.push(`name matches "${table.name}"`);
|
|
240
|
+
} else {
|
|
241
|
+
score += W.nameSubstring;
|
|
242
|
+
reasons.push(`name looks like ${concept}`);
|
|
232
243
|
}
|
|
244
|
+
if (hasDefiningColumn(concept, table.columns)) {
|
|
245
|
+
score += W.definingColumn;
|
|
246
|
+
reasons.push(`has the columns a ${concept} needs`);
|
|
247
|
+
}
|
|
248
|
+
if (table.columnsMeta?.some((c) => c.isPrimaryKey)) {
|
|
249
|
+
score += W.hasPrimaryKey;
|
|
250
|
+
reasons.push("has primary key");
|
|
251
|
+
}
|
|
252
|
+
if (table.rowCount !== void 0) if (table.rowCount === 0) {
|
|
253
|
+
score += W.emptyTable;
|
|
254
|
+
reasons.push("table is empty");
|
|
255
|
+
} else {
|
|
256
|
+
score += W.hasRows;
|
|
257
|
+
reasons.push(`${table.rowCount.toLocaleString("en-US")} rows`);
|
|
258
|
+
}
|
|
259
|
+
out.push({
|
|
260
|
+
value: concept,
|
|
261
|
+
score,
|
|
262
|
+
tier: tierFor(score, CONCEPT_TIERS),
|
|
263
|
+
reasons
|
|
264
|
+
});
|
|
233
265
|
}
|
|
234
|
-
return
|
|
266
|
+
return out.sort((a, b) => b.score - a.score);
|
|
267
|
+
}
|
|
268
|
+
function topConcept(table) {
|
|
269
|
+
const ranked = scoreConcepts(table);
|
|
270
|
+
const best = ranked[0];
|
|
271
|
+
if (!best) return null;
|
|
272
|
+
return {
|
|
273
|
+
best,
|
|
274
|
+
ambiguous: isAmbiguous(ranked)
|
|
275
|
+
};
|
|
235
276
|
}
|
|
236
277
|
function requiredFieldsFor(concept) {
|
|
278
|
+
if (concept === "custom") return [];
|
|
237
279
|
return CONCEPT_FIELDS[concept].filter((f) => f.required).map((f) => f.field);
|
|
238
280
|
}
|
|
239
281
|
function validateMapping(requiredFields, fieldMap) {
|
|
@@ -261,14 +303,6 @@ function suggestDiscriminatorColumns(columns) {
|
|
|
261
303
|
}
|
|
262
304
|
return out;
|
|
263
305
|
}
|
|
264
|
-
const CONTACT_FK_HINTS = [
|
|
265
|
-
"user_id",
|
|
266
|
-
"owner_id",
|
|
267
|
-
"contact_id",
|
|
268
|
-
"member_id",
|
|
269
|
-
"person_id",
|
|
270
|
-
"created_by"
|
|
271
|
-
];
|
|
272
306
|
const COMPANY_FK_HINTS = [
|
|
273
307
|
"organization_id",
|
|
274
308
|
"organisation_id",
|
|
@@ -286,50 +320,112 @@ function firstMatchingColumn(columns, hints) {
|
|
|
286
320
|
return null;
|
|
287
321
|
}
|
|
288
322
|
/**
|
|
289
|
-
* Suggest contact→company relationships
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
323
|
+
* Suggest contact→company relationships. When the source table carries FK
|
|
324
|
+
* metadata (`references`), the target table is a *fact*, not a name guess —
|
|
325
|
+
* that scores highest. Without metadata (v1 hosts) we fall back to the legacy
|
|
326
|
+
* FK-name hints at a lower tier.
|
|
293
327
|
*/
|
|
294
|
-
function
|
|
295
|
-
const companyTables = allTables.filter((t) => t.name !== source.name &&
|
|
328
|
+
function scoreRelationships(source, allTables) {
|
|
329
|
+
const companyTables = allTables.filter((t) => t.name !== source.name && topConcept(t)?.best.value === "company");
|
|
296
330
|
if (companyTables.length === 0) return [];
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
331
|
+
const companyNames = new Set(companyTables.map((t) => t.name.toLowerCase()));
|
|
332
|
+
const out = [];
|
|
333
|
+
const seenTargets = /* @__PURE__ */ new Set();
|
|
334
|
+
for (const col of source.columnsMeta ?? []) {
|
|
335
|
+
const ref = col.references;
|
|
336
|
+
if (!(ref && companyNames.has(ref.table.toLowerCase()))) continue;
|
|
337
|
+
if (seenTargets.has(ref.table.toLowerCase())) continue;
|
|
338
|
+
seenTargets.add(ref.table.toLowerCase());
|
|
339
|
+
out.push({
|
|
340
|
+
value: {
|
|
341
|
+
name: "company",
|
|
342
|
+
entityTable: ref.table,
|
|
343
|
+
via: {
|
|
344
|
+
fkOn: "contact",
|
|
345
|
+
column: `${source.name}.${col.name}`
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
score: W.fkResolved,
|
|
349
|
+
tier: tierFor(W.fkResolved, RELATIONSHIP_TIERS),
|
|
350
|
+
reasons: [`foreign key → ${ref.table}.${ref.column}`]
|
|
316
351
|
});
|
|
317
352
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (localKey && foreignKey) suggestions.push({
|
|
353
|
+
if (source.columnsMeta?.length) return out.sort((a, b) => b.score - a.score);
|
|
354
|
+
const contactFk = firstMatchingColumn(source.columns, COMPANY_FK_HINTS);
|
|
355
|
+
if (contactFk) out.push({
|
|
356
|
+
value: {
|
|
323
357
|
name: "company",
|
|
324
|
-
entityTable: companyTables
|
|
358
|
+
entityTable: companyTables[0]?.name ?? "",
|
|
325
359
|
via: {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
foreignKey
|
|
360
|
+
fkOn: "contact",
|
|
361
|
+
column: `${source.name}.${contactFk}`
|
|
329
362
|
}
|
|
363
|
+
},
|
|
364
|
+
score: W.fkNameHint,
|
|
365
|
+
tier: tierFor(W.fkNameHint, RELATIONSHIP_TIERS),
|
|
366
|
+
reasons: [`column "${contactFk}" looks like a company link`]
|
|
367
|
+
});
|
|
368
|
+
return out.sort((a, b) => b.score - a.score);
|
|
369
|
+
}
|
|
370
|
+
const FIELD_TYPE_EXPECTATION = {
|
|
371
|
+
name: "text",
|
|
372
|
+
email: "text",
|
|
373
|
+
domain: "text",
|
|
374
|
+
stage: "text",
|
|
375
|
+
status: "text",
|
|
376
|
+
subject: "text",
|
|
377
|
+
body: "text",
|
|
378
|
+
summary: "text",
|
|
379
|
+
type: "text",
|
|
380
|
+
value: "numeric",
|
|
381
|
+
createdAt: "timestamp"
|
|
382
|
+
};
|
|
383
|
+
function typeFits(field, type) {
|
|
384
|
+
const expectation = FIELD_TYPE_EXPECTATION[field];
|
|
385
|
+
if (!(expectation && type)) return null;
|
|
386
|
+
if (expectation === "text") return isTextLike(type);
|
|
387
|
+
if (expectation === "numeric") return isNumericLike(type);
|
|
388
|
+
return isTimestampLike(type);
|
|
389
|
+
}
|
|
390
|
+
function scoreFieldMap(concept, table) {
|
|
391
|
+
const lowerToActual = new Map(table.columns.map((c) => [c.toLowerCase(), c]));
|
|
392
|
+
const out = [];
|
|
393
|
+
for (const { field, candidates, required } of CONCEPT_FIELDS[concept]) for (const candidate of candidates) {
|
|
394
|
+
const column = lowerToActual.get(candidate);
|
|
395
|
+
if (!column) continue;
|
|
396
|
+
let score = W.fieldNameMatch;
|
|
397
|
+
const reasons = [`name matches "${column}"`];
|
|
398
|
+
const meta = metaFor(table, column);
|
|
399
|
+
const fits = typeFits(field, meta?.type);
|
|
400
|
+
if (fits === true) {
|
|
401
|
+
score += W.typeCompatible;
|
|
402
|
+
reasons.push(`type ${meta?.type}`);
|
|
403
|
+
} else if (fits === false) {
|
|
404
|
+
score += W.typeMismatch;
|
|
405
|
+
reasons.push(`type ${meta?.type} does not fit ${field}`);
|
|
406
|
+
}
|
|
407
|
+
if (required && meta?.nullable === false) {
|
|
408
|
+
score += W.requiredNotNull;
|
|
409
|
+
reasons.push("not null");
|
|
410
|
+
}
|
|
411
|
+
out.push({
|
|
412
|
+
value: {
|
|
413
|
+
field,
|
|
414
|
+
column,
|
|
415
|
+
required
|
|
416
|
+
},
|
|
417
|
+
score,
|
|
418
|
+
tier: tierFor(score, FIELD_TIERS),
|
|
419
|
+
reasons
|
|
330
420
|
});
|
|
421
|
+
break;
|
|
331
422
|
}
|
|
332
|
-
return
|
|
423
|
+
return out;
|
|
424
|
+
}
|
|
425
|
+
function toFieldMap(scored) {
|
|
426
|
+
const map = {};
|
|
427
|
+
for (const { value } of scored) map[value.field] = value.column;
|
|
428
|
+
return map;
|
|
333
429
|
}
|
|
334
430
|
//#endregion
|
|
335
|
-
export { TARGET_CONCEPTS, matchesConcept, requiredFieldsFor,
|
|
431
|
+
export { TARGET_CONCEPTS, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
package/dist/index.cjs
CHANGED
|
@@ -8,6 +8,7 @@ const require_sync_engine = require("./sync-engine.cjs");
|
|
|
8
8
|
exports.CANONICAL_FIELDS = require_entity_config.CANONICAL_FIELDS;
|
|
9
9
|
exports.CRM_CONCEPTS = require_entity_config.CRM_CONCEPTS;
|
|
10
10
|
exports.ClivlyConfigError = require_entity_config.ClivlyConfigError;
|
|
11
|
+
exports.MATERIALIZING_CONCEPTS = require_entity_config.MATERIALIZING_CONCEPTS;
|
|
11
12
|
exports.authCustom = require_auth_adapter.authCustom;
|
|
12
13
|
exports.buildFilterFromDiscriminator = require_mapping_form.buildFilterFromDiscriminator;
|
|
13
14
|
exports.buildRelationships = require_mapping_form.buildRelationships;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Activity, ActivityType, Company, Contact, ContactFilters, ContactStatus, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, CrmRole, Deal, DealStage, Note, Task, TaskEntityType, TaskFilters, TaskPriority, TaskStatus, UpdateContactInput, UpdateDealInput, UpdateTaskInput } from "./types.cjs";
|
|
1
|
+
import { Activity, ActivityType, Company, Contact, ContactFilters, ContactStatus, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, CrmRole, Deal, DealStage, Note, RecordSource, Task, TaskEntityType, TaskFilters, TaskPriority, TaskStatus, UpdateContactInput, UpdateDealInput, UpdateTaskInput } from "./types.cjs";
|
|
2
2
|
import { CRMAdapter } from "./adapter.cjs";
|
|
3
3
|
import { ClivlyAuthAdapter, ClivlyUser, CrmMembership, MembershipResolver, authCustom } from "./auth-adapter.cjs";
|
|
4
|
-
import { CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, ClivlyEntitiesConfig, ClivlyEntityConfig, ConfigValidationError, ConfigValidationResult, CrmConcept, DiscoveredSchemaTable, FilterExpr, FilterValue, RelationshipSpec, RelationshipVia, RelationshipsMap, defineClivlyConfig, entitiesConfigSchema, filterSchema, relationshipSchema, relationshipsSchema, validateEntitiesConfig } from "./entity-config.cjs";
|
|
4
|
+
import { CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, ClivlyEntitiesConfig, ClivlyEntityConfig, ConfigValidationError, ConfigValidationResult, CrmConcept, DiscoveredSchemaTable, FilterExpr, FilterValue, MATERIALIZING_CONCEPTS, MaterializingConcept, RelationshipSpec, RelationshipVia, RelationshipsMap, defineClivlyConfig, entitiesConfigSchema, filterSchema, relationshipSchema, relationshipsSchema, validateEntitiesConfig } from "./entity-config.cjs";
|
|
5
5
|
import { RelationshipCandidate, buildFilterFromDiscriminator, buildRelationships, relationshipViaSignature } from "./mapping-form.cjs";
|
|
6
6
|
import { EntityMappingRow, mappingsToEntitiesConfig } from "./mappings-config.cjs";
|
|
7
7
|
import { CompileOptions, CompiledView, ExplainResult, compileEntityView, compileEntityViews, explainEntitiesConfig } from "./view-compiler.cjs";
|
|
8
8
|
import { EntitySyncResult, MirrorRecord, PersistAction, SourceRow, SyncAction, SyncResult, SyncStore, reconcile, runSync } from "./sync-engine.cjs";
|
|
9
|
-
export { Activity, ActivityType, CANONICAL_FIELDS, type CRMAdapter, CRM_CONCEPTS, type ClivlyAuthAdapter, ClivlyConfigError, type ClivlyEntitiesConfig, type ClivlyEntityConfig, type ClivlyUser, Company, type CompileOptions, type CompiledView, type ConfigValidationError, type ConfigValidationResult, Contact, ContactFilters, ContactStatus, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, type CrmConcept, type CrmMembership, CrmRole, Deal, DealStage, type DiscoveredSchemaTable, type EntityMappingRow, type EntitySyncResult, type ExplainResult, type FilterExpr, type FilterValue, type MembershipResolver, type MirrorRecord, Note, type PersistAction, type RelationshipCandidate, type RelationshipSpec, type RelationshipVia, type RelationshipsMap, type SourceRow, type SyncAction, type SyncResult, type SyncStore, Task, TaskEntityType, TaskFilters, TaskPriority, TaskStatus, UpdateContactInput, UpdateDealInput, UpdateTaskInput, authCustom, buildFilterFromDiscriminator, buildRelationships, compileEntityView, compileEntityViews, defineClivlyConfig, entitiesConfigSchema, explainEntitiesConfig, filterSchema, mappingsToEntitiesConfig, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, runSync, validateEntitiesConfig };
|
|
9
|
+
export { Activity, ActivityType, CANONICAL_FIELDS, type CRMAdapter, CRM_CONCEPTS, type ClivlyAuthAdapter, ClivlyConfigError, type ClivlyEntitiesConfig, type ClivlyEntityConfig, type ClivlyUser, Company, type CompileOptions, type CompiledView, type ConfigValidationError, type ConfigValidationResult, Contact, ContactFilters, ContactStatus, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, type CrmConcept, type CrmMembership, CrmRole, Deal, DealStage, type DiscoveredSchemaTable, type EntityMappingRow, type EntitySyncResult, type ExplainResult, type FilterExpr, type FilterValue, MATERIALIZING_CONCEPTS, type MaterializingConcept, type MembershipResolver, type MirrorRecord, Note, type PersistAction, RecordSource, type RelationshipCandidate, type RelationshipSpec, type RelationshipVia, type RelationshipsMap, type SourceRow, type SyncAction, type SyncResult, type SyncStore, Task, TaskEntityType, TaskFilters, TaskPriority, TaskStatus, UpdateContactInput, UpdateDealInput, UpdateTaskInput, authCustom, buildFilterFromDiscriminator, buildRelationships, compileEntityView, compileEntityViews, defineClivlyConfig, entitiesConfigSchema, explainEntitiesConfig, filterSchema, mappingsToEntitiesConfig, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, runSync, validateEntitiesConfig };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Activity, ActivityType, Company, Contact, ContactFilters, ContactStatus, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, CrmRole, Deal, DealStage, Note, Task, TaskEntityType, TaskFilters, TaskPriority, TaskStatus, UpdateContactInput, UpdateDealInput, UpdateTaskInput } from "./types.mjs";
|
|
1
|
+
import { Activity, ActivityType, Company, Contact, ContactFilters, ContactStatus, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, CrmRole, Deal, DealStage, Note, RecordSource, Task, TaskEntityType, TaskFilters, TaskPriority, TaskStatus, UpdateContactInput, UpdateDealInput, UpdateTaskInput } from "./types.mjs";
|
|
2
2
|
import { CRMAdapter } from "./adapter.mjs";
|
|
3
3
|
import { ClivlyAuthAdapter, ClivlyUser, CrmMembership, MembershipResolver, authCustom } from "./auth-adapter.mjs";
|
|
4
|
-
import { CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, ClivlyEntitiesConfig, ClivlyEntityConfig, ConfigValidationError, ConfigValidationResult, CrmConcept, DiscoveredSchemaTable, FilterExpr, FilterValue, RelationshipSpec, RelationshipVia, RelationshipsMap, defineClivlyConfig, entitiesConfigSchema, filterSchema, relationshipSchema, relationshipsSchema, validateEntitiesConfig } from "./entity-config.mjs";
|
|
4
|
+
import { CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, ClivlyEntitiesConfig, ClivlyEntityConfig, ConfigValidationError, ConfigValidationResult, CrmConcept, DiscoveredSchemaTable, FilterExpr, FilterValue, MATERIALIZING_CONCEPTS, MaterializingConcept, RelationshipSpec, RelationshipVia, RelationshipsMap, defineClivlyConfig, entitiesConfigSchema, filterSchema, relationshipSchema, relationshipsSchema, validateEntitiesConfig } from "./entity-config.mjs";
|
|
5
5
|
import { RelationshipCandidate, buildFilterFromDiscriminator, buildRelationships, relationshipViaSignature } from "./mapping-form.mjs";
|
|
6
6
|
import { EntityMappingRow, mappingsToEntitiesConfig } from "./mappings-config.mjs";
|
|
7
7
|
import { CompileOptions, CompiledView, ExplainResult, compileEntityView, compileEntityViews, explainEntitiesConfig } from "./view-compiler.mjs";
|
|
8
8
|
import { EntitySyncResult, MirrorRecord, PersistAction, SourceRow, SyncAction, SyncResult, SyncStore, reconcile, runSync } from "./sync-engine.mjs";
|
|
9
|
-
export { Activity, ActivityType, CANONICAL_FIELDS, type CRMAdapter, CRM_CONCEPTS, type ClivlyAuthAdapter, ClivlyConfigError, type ClivlyEntitiesConfig, type ClivlyEntityConfig, type ClivlyUser, Company, type CompileOptions, type CompiledView, type ConfigValidationError, type ConfigValidationResult, Contact, ContactFilters, ContactStatus, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, type CrmConcept, type CrmMembership, CrmRole, Deal, DealStage, type DiscoveredSchemaTable, type EntityMappingRow, type EntitySyncResult, type ExplainResult, type FilterExpr, type FilterValue, type MembershipResolver, type MirrorRecord, Note, type PersistAction, type RelationshipCandidate, type RelationshipSpec, type RelationshipVia, type RelationshipsMap, type SourceRow, type SyncAction, type SyncResult, type SyncStore, Task, TaskEntityType, TaskFilters, TaskPriority, TaskStatus, UpdateContactInput, UpdateDealInput, UpdateTaskInput, authCustom, buildFilterFromDiscriminator, buildRelationships, compileEntityView, compileEntityViews, defineClivlyConfig, entitiesConfigSchema, explainEntitiesConfig, filterSchema, mappingsToEntitiesConfig, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, runSync, validateEntitiesConfig };
|
|
9
|
+
export { Activity, ActivityType, CANONICAL_FIELDS, type CRMAdapter, CRM_CONCEPTS, type ClivlyAuthAdapter, ClivlyConfigError, type ClivlyEntitiesConfig, type ClivlyEntityConfig, type ClivlyUser, Company, type CompileOptions, type CompiledView, type ConfigValidationError, type ConfigValidationResult, Contact, ContactFilters, ContactStatus, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, type CrmConcept, type CrmMembership, CrmRole, Deal, DealStage, type DiscoveredSchemaTable, type EntityMappingRow, type EntitySyncResult, type ExplainResult, type FilterExpr, type FilterValue, MATERIALIZING_CONCEPTS, type MaterializingConcept, type MembershipResolver, type MirrorRecord, Note, type PersistAction, RecordSource, type RelationshipCandidate, type RelationshipSpec, type RelationshipVia, type RelationshipsMap, type SourceRow, type SyncAction, type SyncResult, type SyncStore, Task, TaskEntityType, TaskFilters, TaskPriority, TaskStatus, UpdateContactInput, UpdateDealInput, UpdateTaskInput, authCustom, buildFilterFromDiscriminator, buildRelationships, compileEntityView, compileEntityViews, defineClivlyConfig, entitiesConfigSchema, explainEntitiesConfig, filterSchema, mappingsToEntitiesConfig, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, runSync, validateEntitiesConfig };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { authCustom } from "./auth-adapter.mjs";
|
|
2
|
-
import { CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, defineClivlyConfig, entitiesConfigSchema, filterSchema, relationshipSchema, relationshipsSchema, validateEntitiesConfig } from "./entity-config.mjs";
|
|
2
|
+
import { CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, MATERIALIZING_CONCEPTS, defineClivlyConfig, entitiesConfigSchema, filterSchema, relationshipSchema, relationshipsSchema, validateEntitiesConfig } from "./entity-config.mjs";
|
|
3
3
|
import { buildFilterFromDiscriminator, buildRelationships, relationshipViaSignature } from "./mapping-form.mjs";
|
|
4
4
|
import { mappingsToEntitiesConfig } from "./mappings-config.mjs";
|
|
5
5
|
import { compileEntityView, compileEntityViews, explainEntitiesConfig } from "./view-compiler.mjs";
|
|
6
6
|
import { reconcile, runSync } from "./sync-engine.mjs";
|
|
7
|
-
export { CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, authCustom, buildFilterFromDiscriminator, buildRelationships, compileEntityView, compileEntityViews, defineClivlyConfig, entitiesConfigSchema, explainEntitiesConfig, filterSchema, mappingsToEntitiesConfig, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, runSync, validateEntitiesConfig };
|
|
7
|
+
export { CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, MATERIALIZING_CONCEPTS, authCustom, buildFilterFromDiscriminator, buildRelationships, compileEntityView, compileEntityViews, defineClivlyConfig, entitiesConfigSchema, explainEntitiesConfig, filterSchema, mappingsToEntitiesConfig, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, runSync, validateEntitiesConfig };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/mapping-score.ts
|
|
3
|
+
const CONCEPT_TIERS = {
|
|
4
|
+
high: 70,
|
|
5
|
+
medium: 40
|
|
6
|
+
};
|
|
7
|
+
const FIELD_TIERS = {
|
|
8
|
+
high: 70,
|
|
9
|
+
medium: 40
|
|
10
|
+
};
|
|
11
|
+
const RELATIONSHIP_TIERS = {
|
|
12
|
+
high: 60,
|
|
13
|
+
medium: 30
|
|
14
|
+
};
|
|
15
|
+
/** Two concept candidates closer than this are treated as ambiguous. */
|
|
16
|
+
const AMBIGUITY_MARGIN = 15;
|
|
17
|
+
/** Signal weights. Tuned against fixtures; adjust here, not at call sites. */
|
|
18
|
+
const W = {
|
|
19
|
+
nameExact: 50,
|
|
20
|
+
nameSubstring: 25,
|
|
21
|
+
definingColumn: 20,
|
|
22
|
+
hasPrimaryKey: 10,
|
|
23
|
+
hasRows: 10,
|
|
24
|
+
emptyTable: -20,
|
|
25
|
+
fieldNameMatch: 50,
|
|
26
|
+
typeCompatible: 30,
|
|
27
|
+
typeMismatch: -30,
|
|
28
|
+
requiredNotNull: 10,
|
|
29
|
+
fkResolved: 60,
|
|
30
|
+
fkNameHint: 30,
|
|
31
|
+
joinTable: 20
|
|
32
|
+
};
|
|
33
|
+
function tierFor(score, t) {
|
|
34
|
+
if (score >= t.high) return "High";
|
|
35
|
+
if (score >= t.medium) return "Medium";
|
|
36
|
+
return "Low";
|
|
37
|
+
}
|
|
38
|
+
const TEXT_TYPES = [
|
|
39
|
+
"text",
|
|
40
|
+
"varchar",
|
|
41
|
+
"char",
|
|
42
|
+
"citext",
|
|
43
|
+
"uuid",
|
|
44
|
+
"string"
|
|
45
|
+
];
|
|
46
|
+
const NUMERIC_TYPES = [
|
|
47
|
+
"numeric",
|
|
48
|
+
"decimal",
|
|
49
|
+
"int",
|
|
50
|
+
"integer",
|
|
51
|
+
"bigint",
|
|
52
|
+
"smallint",
|
|
53
|
+
"float",
|
|
54
|
+
"double",
|
|
55
|
+
"real",
|
|
56
|
+
"money"
|
|
57
|
+
];
|
|
58
|
+
const TIMESTAMP_TYPES = [
|
|
59
|
+
"timestamp",
|
|
60
|
+
"timestamptz",
|
|
61
|
+
"date",
|
|
62
|
+
"datetime"
|
|
63
|
+
];
|
|
64
|
+
function matchesType(type, list) {
|
|
65
|
+
if (!type) return false;
|
|
66
|
+
const normalized = type.toLowerCase();
|
|
67
|
+
return list.some((t) => normalized.startsWith(t));
|
|
68
|
+
}
|
|
69
|
+
function isTextLike(type) {
|
|
70
|
+
return matchesType(type, TEXT_TYPES);
|
|
71
|
+
}
|
|
72
|
+
function isNumericLike(type) {
|
|
73
|
+
return matchesType(type, NUMERIC_TYPES);
|
|
74
|
+
}
|
|
75
|
+
function isTimestampLike(type) {
|
|
76
|
+
return matchesType(type, TIMESTAMP_TYPES);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* True when the top two candidates are too close to call. Callers must never
|
|
80
|
+
* one-click apply an ambiguous result — the user picks.
|
|
81
|
+
*/
|
|
82
|
+
function isAmbiguous(ranked) {
|
|
83
|
+
const [first, second] = ranked;
|
|
84
|
+
if (!(first && second)) return false;
|
|
85
|
+
return first.score - second.score < 15;
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
exports.AMBIGUITY_MARGIN = AMBIGUITY_MARGIN;
|
|
89
|
+
exports.CONCEPT_TIERS = CONCEPT_TIERS;
|
|
90
|
+
exports.FIELD_TIERS = FIELD_TIERS;
|
|
91
|
+
exports.RELATIONSHIP_TIERS = RELATIONSHIP_TIERS;
|
|
92
|
+
exports.W = W;
|
|
93
|
+
exports.isAmbiguous = isAmbiguous;
|
|
94
|
+
exports.isNumericLike = isNumericLike;
|
|
95
|
+
exports.isTextLike = isTextLike;
|
|
96
|
+
exports.isTimestampLike = isTimestampLike;
|
|
97
|
+
exports.tierFor = tierFor;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region 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 { AMBIGUITY_MARGIN, CONCEPT_TIERS, FIELD_TIERS, RELATIONSHIP_TIERS, Scored, Thresholds, Tier, W, isAmbiguous, isNumericLike, isTextLike, isTimestampLike, tierFor };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region 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 { AMBIGUITY_MARGIN, CONCEPT_TIERS, FIELD_TIERS, RELATIONSHIP_TIERS, Scored, Thresholds, Tier, W, isAmbiguous, isNumericLike, isTextLike, isTimestampLike, tierFor };
|