@clivly/core 0.1.0 → 0.2.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 +57 -0
- package/dist/adapter.cjs +0 -0
- package/dist/adapter.d.cts +36 -0
- package/dist/adapter.d.mts +4 -1
- package/dist/auth-adapter.cjs +7 -0
- package/dist/auth-adapter.d.cts +30 -0
- package/dist/drizzle.cjs +32 -0
- package/dist/drizzle.d.cts +21 -0
- package/dist/drizzle.d.mts +21 -0
- package/dist/drizzle.mjs +31 -0
- package/dist/entity-config.cjs +314 -0
- package/dist/entity-config.d.cts +272 -0
- package/dist/entity-config.d.mts +18 -4
- package/dist/entity-config.mjs +49 -10
- package/dist/entity-heuristics.cjs +344 -0
- package/dist/entity-heuristics.d.cts +43 -0
- package/dist/entity-heuristics.d.mts +43 -0
- package/dist/entity-heuristics.mjs +335 -0
- package/dist/index.cjs +26 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +3 -3
- package/dist/mapping-form.cjs +50 -0
- package/dist/mapping-form.d.cts +35 -0
- package/dist/mappings-config.cjs +99 -0
- package/dist/mappings-config.d.cts +41 -0
- package/dist/mappings-config.mjs +20 -1
- package/dist/sync-engine.cjs +161 -0
- package/dist/sync-engine.d.cts +148 -0
- package/dist/sync-engine.d.mts +14 -4
- package/dist/sync-engine.mjs +39 -18
- package/dist/types.cjs +0 -0
- package/dist/types.d.cts +156 -0
- package/dist/types.d.mts +9 -1
- package/dist/view-compiler.cjs +188 -0
- package/dist/view-compiler.d.cts +64 -0
- package/dist/view-compiler.d.mts +22 -1
- package/dist/view-compiler.mjs +31 -1
- package/package.json +106 -23
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/entity-heuristics.ts
|
|
3
|
+
const TARGET_CONCEPTS = [
|
|
4
|
+
"contact",
|
|
5
|
+
"company",
|
|
6
|
+
"deal",
|
|
7
|
+
"activity",
|
|
8
|
+
"conversation",
|
|
9
|
+
"note"
|
|
10
|
+
];
|
|
11
|
+
const TABLE_PATTERNS = [
|
|
12
|
+
{
|
|
13
|
+
concept: "contact",
|
|
14
|
+
names: [
|
|
15
|
+
"users",
|
|
16
|
+
"customers",
|
|
17
|
+
"clients",
|
|
18
|
+
"accounts",
|
|
19
|
+
"participants",
|
|
20
|
+
"attendees",
|
|
21
|
+
"people",
|
|
22
|
+
"members",
|
|
23
|
+
"contacts"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
concept: "company",
|
|
28
|
+
names: [
|
|
29
|
+
"companies",
|
|
30
|
+
"organizations",
|
|
31
|
+
"organisations",
|
|
32
|
+
"orgs",
|
|
33
|
+
"teams",
|
|
34
|
+
"businesses",
|
|
35
|
+
"accounts"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
concept: "deal",
|
|
40
|
+
names: [
|
|
41
|
+
"deals",
|
|
42
|
+
"opportunities",
|
|
43
|
+
"subscriptions",
|
|
44
|
+
"orders",
|
|
45
|
+
"invoices",
|
|
46
|
+
"sales"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
concept: "conversation",
|
|
51
|
+
names: [
|
|
52
|
+
"conversations",
|
|
53
|
+
"messages",
|
|
54
|
+
"tickets",
|
|
55
|
+
"threads",
|
|
56
|
+
"chats",
|
|
57
|
+
"inbox"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
concept: "activity",
|
|
62
|
+
names: [
|
|
63
|
+
"events",
|
|
64
|
+
"activities",
|
|
65
|
+
"jobs",
|
|
66
|
+
"logs",
|
|
67
|
+
"audit",
|
|
68
|
+
"shifts",
|
|
69
|
+
"scans",
|
|
70
|
+
"sessions"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
concept: "note",
|
|
75
|
+
names: [
|
|
76
|
+
"notes",
|
|
77
|
+
"comments",
|
|
78
|
+
"annotations"
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
const CONCEPT_FIELDS = {
|
|
83
|
+
contact: [{
|
|
84
|
+
field: "name",
|
|
85
|
+
candidates: [
|
|
86
|
+
"name",
|
|
87
|
+
"full_name",
|
|
88
|
+
"fullname",
|
|
89
|
+
"display_name"
|
|
90
|
+
],
|
|
91
|
+
required: false
|
|
92
|
+
}, {
|
|
93
|
+
field: "email",
|
|
94
|
+
candidates: ["email", "email_address"],
|
|
95
|
+
required: true
|
|
96
|
+
}],
|
|
97
|
+
company: [{
|
|
98
|
+
field: "name",
|
|
99
|
+
candidates: [
|
|
100
|
+
"name",
|
|
101
|
+
"company_name",
|
|
102
|
+
"legal_name",
|
|
103
|
+
"title"
|
|
104
|
+
],
|
|
105
|
+
required: true
|
|
106
|
+
}, {
|
|
107
|
+
field: "domain",
|
|
108
|
+
candidates: [
|
|
109
|
+
"domain",
|
|
110
|
+
"website",
|
|
111
|
+
"url"
|
|
112
|
+
],
|
|
113
|
+
required: false
|
|
114
|
+
}],
|
|
115
|
+
deal: [
|
|
116
|
+
{
|
|
117
|
+
field: "name",
|
|
118
|
+
candidates: ["name", "title"],
|
|
119
|
+
required: false
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
field: "value",
|
|
123
|
+
candidates: [
|
|
124
|
+
"value",
|
|
125
|
+
"amount",
|
|
126
|
+
"total",
|
|
127
|
+
"price"
|
|
128
|
+
],
|
|
129
|
+
required: false
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
field: "stage",
|
|
133
|
+
candidates: [
|
|
134
|
+
"stage",
|
|
135
|
+
"status",
|
|
136
|
+
"state"
|
|
137
|
+
],
|
|
138
|
+
required: false
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
activity: [
|
|
142
|
+
{
|
|
143
|
+
field: "type",
|
|
144
|
+
candidates: [
|
|
145
|
+
"type",
|
|
146
|
+
"kind",
|
|
147
|
+
"event_type"
|
|
148
|
+
],
|
|
149
|
+
required: false
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
field: "summary",
|
|
153
|
+
candidates: [
|
|
154
|
+
"summary",
|
|
155
|
+
"description",
|
|
156
|
+
"message",
|
|
157
|
+
"title"
|
|
158
|
+
],
|
|
159
|
+
required: false
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
field: "createdAt",
|
|
163
|
+
candidates: [
|
|
164
|
+
"created_at",
|
|
165
|
+
"createdat",
|
|
166
|
+
"timestamp"
|
|
167
|
+
],
|
|
168
|
+
required: false
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
conversation: [{
|
|
172
|
+
field: "status",
|
|
173
|
+
candidates: ["status", "state"],
|
|
174
|
+
required: false
|
|
175
|
+
}, {
|
|
176
|
+
field: "subject",
|
|
177
|
+
candidates: [
|
|
178
|
+
"subject",
|
|
179
|
+
"title",
|
|
180
|
+
"preview"
|
|
181
|
+
],
|
|
182
|
+
required: false
|
|
183
|
+
}],
|
|
184
|
+
note: [{
|
|
185
|
+
field: "body",
|
|
186
|
+
candidates: [
|
|
187
|
+
"body",
|
|
188
|
+
"content",
|
|
189
|
+
"text",
|
|
190
|
+
"note"
|
|
191
|
+
],
|
|
192
|
+
required: true
|
|
193
|
+
}]
|
|
194
|
+
};
|
|
195
|
+
const CURSOR_CANDIDATES = [
|
|
196
|
+
"updatedat",
|
|
197
|
+
"updated_at",
|
|
198
|
+
"modifiedat",
|
|
199
|
+
"modified_at",
|
|
200
|
+
"createdat",
|
|
201
|
+
"created_at"
|
|
202
|
+
];
|
|
203
|
+
function suggestCursorField(columnKeys) {
|
|
204
|
+
const lowerToActual = new Map(columnKeys.map((c) => [c.toLowerCase(), c]));
|
|
205
|
+
for (const candidate of CURSOR_CANDIDATES) {
|
|
206
|
+
const match = lowerToActual.get(candidate);
|
|
207
|
+
if (match) return match;
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
function suggestConcept(tableName) {
|
|
212
|
+
const normalized = tableName.toLowerCase();
|
|
213
|
+
for (const { concept, names } of TABLE_PATTERNS) if (names.includes(normalized)) return concept;
|
|
214
|
+
for (const { concept, names } of TABLE_PATTERNS) if (names.some((name) => normalized.includes(name))) return concept;
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
function matchesConcept(tableName, concept) {
|
|
218
|
+
const normalized = tableName.toLowerCase();
|
|
219
|
+
const entry = TABLE_PATTERNS.find((p) => p.concept === concept);
|
|
220
|
+
if (!entry) return null;
|
|
221
|
+
if (entry.names.includes(normalized)) return { exact: true };
|
|
222
|
+
if (entry.names.some((name) => normalized.includes(name))) return { exact: false };
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
function suggestFieldMap(concept, columns) {
|
|
226
|
+
const lowerToActual = new Map(columns.map((c) => [c.toLowerCase(), c]));
|
|
227
|
+
const map = {};
|
|
228
|
+
for (const { field, candidates } of CONCEPT_FIELDS[concept]) for (const candidate of candidates) {
|
|
229
|
+
const match = lowerToActual.get(candidate);
|
|
230
|
+
if (match) {
|
|
231
|
+
map[field] = match;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return map;
|
|
236
|
+
}
|
|
237
|
+
function requiredFieldsFor(concept) {
|
|
238
|
+
return CONCEPT_FIELDS[concept].filter((f) => f.required).map((f) => f.field);
|
|
239
|
+
}
|
|
240
|
+
function validateMapping(requiredFields, fieldMap) {
|
|
241
|
+
const missing = requiredFields.filter((field) => !fieldMap[field]);
|
|
242
|
+
return {
|
|
243
|
+
valid: missing.length === 0,
|
|
244
|
+
missing
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const DISCRIMINATOR_COLUMNS = [
|
|
248
|
+
"role",
|
|
249
|
+
"type",
|
|
250
|
+
"kind",
|
|
251
|
+
"status",
|
|
252
|
+
"state",
|
|
253
|
+
"category",
|
|
254
|
+
"tier"
|
|
255
|
+
];
|
|
256
|
+
function suggestDiscriminatorColumns(columns) {
|
|
257
|
+
const lowerToActual = new Map(columns.map((c) => [c.toLowerCase(), c]));
|
|
258
|
+
const out = [];
|
|
259
|
+
for (const name of DISCRIMINATOR_COLUMNS) {
|
|
260
|
+
const match = lowerToActual.get(name);
|
|
261
|
+
if (match) out.push(match);
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
const CONTACT_FK_HINTS = [
|
|
266
|
+
"user_id",
|
|
267
|
+
"owner_id",
|
|
268
|
+
"contact_id",
|
|
269
|
+
"member_id",
|
|
270
|
+
"person_id",
|
|
271
|
+
"created_by"
|
|
272
|
+
];
|
|
273
|
+
const COMPANY_FK_HINTS = [
|
|
274
|
+
"organization_id",
|
|
275
|
+
"organisation_id",
|
|
276
|
+
"org_id",
|
|
277
|
+
"company_id",
|
|
278
|
+
"account_id",
|
|
279
|
+
"team_id"
|
|
280
|
+
];
|
|
281
|
+
function firstMatchingColumn(columns, hints) {
|
|
282
|
+
const lowerToActual = new Map(columns.map((c) => [c.toLowerCase(), c]));
|
|
283
|
+
for (const hint of hints) {
|
|
284
|
+
const match = lowerToActual.get(hint);
|
|
285
|
+
if (match) return match;
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Suggest contact→company relationships for a contact-concept source table by
|
|
291
|
+
* inspecting FK column names across the discovered schema. Detects all three
|
|
292
|
+
* DSL shapes: FK-on-contact, FK-on-company, and a join table. Suggestions are
|
|
293
|
+
* candidates only — the developer confirms.
|
|
294
|
+
*/
|
|
295
|
+
function suggestRelationships(source, allTables) {
|
|
296
|
+
const companyTables = allTables.filter((t) => t.name !== source.name && suggestConcept(t.name) === "company");
|
|
297
|
+
if (companyTables.length === 0) return [];
|
|
298
|
+
const suggestions = [];
|
|
299
|
+
const contactFk = firstMatchingColumn(source.columns, COMPANY_FK_HINTS);
|
|
300
|
+
if (contactFk) suggestions.push({
|
|
301
|
+
name: "company",
|
|
302
|
+
entityTable: companyTables[0]?.name ?? "",
|
|
303
|
+
via: {
|
|
304
|
+
fkOn: "contact",
|
|
305
|
+
column: `${source.name}.${contactFk}`
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
for (const company of companyTables) {
|
|
309
|
+
const companyFk = firstMatchingColumn(company.columns, CONTACT_FK_HINTS);
|
|
310
|
+
if (companyFk) suggestions.push({
|
|
311
|
+
name: "company",
|
|
312
|
+
entityTable: company.name,
|
|
313
|
+
via: {
|
|
314
|
+
fkOn: "company",
|
|
315
|
+
column: `${company.name}.${companyFk}`
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
for (const table of allTables) {
|
|
320
|
+
if (table.name === source.name) continue;
|
|
321
|
+
const localKey = firstMatchingColumn(table.columns, CONTACT_FK_HINTS);
|
|
322
|
+
const foreignKey = firstMatchingColumn(table.columns, COMPANY_FK_HINTS);
|
|
323
|
+
if (localKey && foreignKey) suggestions.push({
|
|
324
|
+
name: "company",
|
|
325
|
+
entityTable: companyTables.find((c) => c.name !== table.name)?.name ?? companyTables[0]?.name ?? "",
|
|
326
|
+
via: {
|
|
327
|
+
through: table.name,
|
|
328
|
+
localKey,
|
|
329
|
+
foreignKey
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return suggestions;
|
|
334
|
+
}
|
|
335
|
+
//#endregion
|
|
336
|
+
exports.TARGET_CONCEPTS = TARGET_CONCEPTS;
|
|
337
|
+
exports.matchesConcept = matchesConcept;
|
|
338
|
+
exports.requiredFieldsFor = requiredFieldsFor;
|
|
339
|
+
exports.suggestConcept = suggestConcept;
|
|
340
|
+
exports.suggestCursorField = suggestCursorField;
|
|
341
|
+
exports.suggestDiscriminatorColumns = suggestDiscriminatorColumns;
|
|
342
|
+
exports.suggestFieldMap = suggestFieldMap;
|
|
343
|
+
exports.suggestRelationships = suggestRelationships;
|
|
344
|
+
exports.validateMapping = validateMapping;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/entity-heuristics.d.ts
|
|
2
|
+
declare const TARGET_CONCEPTS: readonly ["contact", "company", "deal", "activity", "conversation", "note"];
|
|
3
|
+
type TargetConcept = (typeof TARGET_CONCEPTS)[number];
|
|
4
|
+
declare function suggestCursorField(columnKeys: string[]): string | null;
|
|
5
|
+
declare function suggestConcept(tableName: string): TargetConcept | null;
|
|
6
|
+
declare function matchesConcept(tableName: string, concept: TargetConcept): {
|
|
7
|
+
exact: boolean;
|
|
8
|
+
} | null;
|
|
9
|
+
declare function suggestFieldMap(concept: TargetConcept, columns: string[]): Record<string, string>;
|
|
10
|
+
declare function requiredFieldsFor(concept: TargetConcept): string[];
|
|
11
|
+
declare function validateMapping(requiredFields: string[], fieldMap: Record<string, string>): {
|
|
12
|
+
valid: boolean;
|
|
13
|
+
missing: string[];
|
|
14
|
+
};
|
|
15
|
+
declare function suggestDiscriminatorColumns(columns: string[]): string[];
|
|
16
|
+
type SuggestedVia = {
|
|
17
|
+
fkOn: "company" | "contact";
|
|
18
|
+
column: string;
|
|
19
|
+
} | {
|
|
20
|
+
through: string;
|
|
21
|
+
localKey: string;
|
|
22
|
+
foreignKey: string;
|
|
23
|
+
};
|
|
24
|
+
interface RelationshipSuggestion {
|
|
25
|
+
/** The related host table. */
|
|
26
|
+
entityTable: string;
|
|
27
|
+
/** Suggested relationship name (defaults to the related concept). */
|
|
28
|
+
name: string;
|
|
29
|
+
via: SuggestedVia;
|
|
30
|
+
}
|
|
31
|
+
interface HeuristicTable {
|
|
32
|
+
columns: string[];
|
|
33
|
+
name: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Suggest contact→company relationships for a contact-concept source table by
|
|
37
|
+
* inspecting FK column names across the discovered schema. Detects all three
|
|
38
|
+
* DSL shapes: FK-on-contact, FK-on-company, and a join table. Suggestions are
|
|
39
|
+
* candidates only — the developer confirms.
|
|
40
|
+
*/
|
|
41
|
+
declare function suggestRelationships(source: HeuristicTable, allTables: HeuristicTable[]): RelationshipSuggestion[];
|
|
42
|
+
//#endregion
|
|
43
|
+
export { RelationshipSuggestion, SuggestedVia, TARGET_CONCEPTS, TargetConcept, matchesConcept, requiredFieldsFor, suggestConcept, suggestCursorField, suggestDiscriminatorColumns, suggestFieldMap, suggestRelationships, validateMapping };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/entity-heuristics.d.ts
|
|
2
|
+
declare const TARGET_CONCEPTS: readonly ["contact", "company", "deal", "activity", "conversation", "note"];
|
|
3
|
+
type TargetConcept = (typeof TARGET_CONCEPTS)[number];
|
|
4
|
+
declare function suggestCursorField(columnKeys: string[]): string | null;
|
|
5
|
+
declare function suggestConcept(tableName: string): TargetConcept | null;
|
|
6
|
+
declare function matchesConcept(tableName: string, concept: TargetConcept): {
|
|
7
|
+
exact: boolean;
|
|
8
|
+
} | null;
|
|
9
|
+
declare function suggestFieldMap(concept: TargetConcept, columns: string[]): Record<string, string>;
|
|
10
|
+
declare function requiredFieldsFor(concept: TargetConcept): string[];
|
|
11
|
+
declare function validateMapping(requiredFields: string[], fieldMap: Record<string, string>): {
|
|
12
|
+
valid: boolean;
|
|
13
|
+
missing: string[];
|
|
14
|
+
};
|
|
15
|
+
declare function suggestDiscriminatorColumns(columns: string[]): string[];
|
|
16
|
+
type SuggestedVia = {
|
|
17
|
+
fkOn: "company" | "contact";
|
|
18
|
+
column: string;
|
|
19
|
+
} | {
|
|
20
|
+
through: string;
|
|
21
|
+
localKey: string;
|
|
22
|
+
foreignKey: string;
|
|
23
|
+
};
|
|
24
|
+
interface RelationshipSuggestion {
|
|
25
|
+
/** The related host table. */
|
|
26
|
+
entityTable: string;
|
|
27
|
+
/** Suggested relationship name (defaults to the related concept). */
|
|
28
|
+
name: string;
|
|
29
|
+
via: SuggestedVia;
|
|
30
|
+
}
|
|
31
|
+
interface HeuristicTable {
|
|
32
|
+
columns: string[];
|
|
33
|
+
name: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Suggest contact→company relationships for a contact-concept source table by
|
|
37
|
+
* inspecting FK column names across the discovered schema. Detects all three
|
|
38
|
+
* DSL shapes: FK-on-contact, FK-on-company, and a join table. Suggestions are
|
|
39
|
+
* candidates only — the developer confirms.
|
|
40
|
+
*/
|
|
41
|
+
declare function suggestRelationships(source: HeuristicTable, allTables: HeuristicTable[]): RelationshipSuggestion[];
|
|
42
|
+
//#endregion
|
|
43
|
+
export { RelationshipSuggestion, SuggestedVia, TARGET_CONCEPTS, TargetConcept, matchesConcept, requiredFieldsFor, suggestConcept, suggestCursorField, suggestDiscriminatorColumns, suggestFieldMap, suggestRelationships, validateMapping };
|