@clivly/core 0.9.6 → 0.10.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 +30 -10
- package/dist/builder.cjs +599 -0
- package/dist/builder.d.cts +336 -0
- package/dist/builder.d.mts +336 -0
- package/dist/builder.mjs +589 -0
- package/dist/drizzle.cjs +9 -1
- package/dist/drizzle.d.cts +4 -2
- package/dist/drizzle.d.mts +4 -2
- package/dist/drizzle.mjs +9 -1
- package/dist/{entity-config-BAaZOIID.cjs → entity-config-C5jBmG2C.cjs} +115 -71
- package/dist/{entity-config-buOIVtMM.d.cts → entity-config-DCa785OE.d.cts} +27 -1
- package/dist/{entity-config-buOIVtMM.d.mts → entity-config-DCa785OE.d.mts} +27 -1
- package/dist/{entity-config-kusaRiDs.mjs → entity-config-DpHPMFIS.mjs} +104 -42
- package/dist/entity-config.cjs +1 -1
- package/dist/entity-config.d.cts +1 -1
- package/dist/entity-config.d.mts +1 -1
- package/dist/entity-config.mjs +1 -1
- package/dist/entity-heuristics.cjs +34 -1
- package/dist/entity-heuristics.d.cts +8 -2
- package/dist/entity-heuristics.d.mts +8 -2
- package/dist/entity-heuristics.mjs +34 -2
- package/dist/index.cjs +136 -199
- package/dist/index.d.cts +135 -89
- package/dist/index.d.mts +135 -89
- package/dist/index.mjs +124 -188
- package/dist/mapping-form.d.cts +1 -1
- package/dist/mapping-form.d.mts +1 -1
- package/dist/mappings-config.cjs +2 -16
- package/dist/mappings-config.d.cts +1 -1
- package/dist/mappings-config.d.mts +1 -1
- package/dist/mappings-config.mjs +1 -15
- package/dist/{projection-OJYCYmS_.cjs → projection-DFgCoyZl.cjs} +0 -6
- package/dist/{projection-BXw_iiw5.mjs → projection-SCNrLJMV.mjs} +1 -1
- package/dist/schema-meta-Ac7938mT.mjs +46 -0
- package/dist/schema-meta-Cgi0MnHh.cjs +69 -0
- package/dist/sync-engine.cjs +1 -1
- package/dist/sync-engine.d.cts +1 -1
- package/dist/sync-engine.d.mts +1 -1
- package/dist/sync-engine.mjs +1 -1
- package/dist/types.cjs +11 -0
- package/dist/types.d.cts +28 -2
- package/dist/types.d.mts +28 -2
- package/dist/types.mjs +9 -1
- package/dist/{view-compiler-u3GGRgnv.mjs → view-compiler-CLvZ0wJW.mjs} +1 -1
- package/dist/{view-compiler-D30HNRnA.cjs → view-compiler-DP4Fbj7-.cjs} +1 -1
- package/dist/view-compiler.cjs +1 -1
- package/dist/view-compiler.d.cts +1 -1
- package/dist/view-compiler.d.mts +1 -1
- package/dist/view-compiler.mjs +1 -1
- package/package.json +11 -1
|
@@ -1,40 +1,6 @@
|
|
|
1
|
-
const require_projection = require("./projection-
|
|
1
|
+
const require_projection = require("./projection-DFgCoyZl.cjs");
|
|
2
|
+
const require_schema_meta = require("./schema-meta-Cgi0MnHh.cjs");
|
|
2
3
|
let zod = require("zod");
|
|
3
|
-
//#region src/schema-meta.ts
|
|
4
|
-
/**
|
|
5
|
-
* Accessors over a discovered table's schema-v2 metadata.
|
|
6
|
-
*
|
|
7
|
-
* Every helper treats *absent* metadata as **unknown**, never as a negative
|
|
8
|
-
* fact. `coversUniqueKey` returning false on a v1 payload is what makes
|
|
9
|
-
* projection validation fail closed: we cannot prove a join is many-to-one, so
|
|
10
|
-
* we refuse to compile it rather than guess.
|
|
11
|
-
*/
|
|
12
|
-
function columnMetaFor(table, column) {
|
|
13
|
-
return table.columnsMeta?.find((c) => c.name === column);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Every primary-key column. More than one entry means a composite PK, which is
|
|
17
|
-
* why `base_pk` identity is rejected for such tables.
|
|
18
|
-
*/
|
|
19
|
-
function primaryKeyColumns(table) {
|
|
20
|
-
return (table.columnsMeta ?? []).filter((c) => c.isPrimaryKey).map((c) => c.name);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Do `columns` contain every column of at least one unique key on `table`?
|
|
24
|
-
*
|
|
25
|
-
* This is the fan-out test. A join whose ON columns cover a unique key of the
|
|
26
|
-
* joined table matches at most one row per base row; one that does not can
|
|
27
|
-
* match many.
|
|
28
|
-
*/
|
|
29
|
-
function coversUniqueKey(table, columns) {
|
|
30
|
-
const present = new Set(columns);
|
|
31
|
-
return (table.uniqueConstraints ?? []).some((key) => key.length > 0 && key.every((column) => present.has(column)));
|
|
32
|
-
}
|
|
33
|
-
/** Is this table's payload rich enough to validate a projection against? */
|
|
34
|
-
function hasRichMeta(table) {
|
|
35
|
-
return Boolean(table.columnsMeta && table.uniqueConstraints);
|
|
36
|
-
}
|
|
37
|
-
//#endregion
|
|
38
4
|
//#region src/projection-cardinality.ts
|
|
39
5
|
/**
|
|
40
6
|
* Cardinality analysis for projection joins.
|
|
@@ -55,7 +21,7 @@ function joinSideColumns(join) {
|
|
|
55
21
|
return columns;
|
|
56
22
|
}
|
|
57
23
|
function joinFansOut(join, joinTable) {
|
|
58
|
-
return !coversUniqueKey(joinTable, joinSideColumns(join));
|
|
24
|
+
return !require_schema_meta.coversUniqueKey(joinTable, joinSideColumns(join));
|
|
59
25
|
}
|
|
60
26
|
/** Every join in the projection that can multiply rows. */
|
|
61
27
|
function fanOutJoins(projection, tablesByName) {
|
|
@@ -135,10 +101,10 @@ function checkTablesExist(errors, projection, resolver, base) {
|
|
|
135
101
|
ok = false;
|
|
136
102
|
continue;
|
|
137
103
|
}
|
|
138
|
-
if (!hasRichMeta(table)) {
|
|
104
|
+
if (!require_schema_meta.hasRichMeta(table)) {
|
|
139
105
|
errors.push({
|
|
140
106
|
path,
|
|
141
|
-
message: `table "${name}"
|
|
107
|
+
message: `table "${name}" was discovered without column types or unique keys, which a projection needs to prove its id is unique. Redeploy or restart the app so it re-reports its schema (or run \`clivly push-schema\`); if the table is declared by hand as \`{ name, columns }\`, use \`discoverFromDrizzle\` so keys and types come with it.`
|
|
142
108
|
});
|
|
143
109
|
ok = false;
|
|
144
110
|
}
|
|
@@ -183,10 +149,27 @@ function checkRefs(errors, projection, resolver, base, basePk) {
|
|
|
183
149
|
for (const [field, binding] of Object.entries(projection.fieldBindings)) checkRef(errors, resolver, `${base}.projection.fieldBindings.${field}`, binding.from);
|
|
184
150
|
for (const ref of require_projection.identityRefs(projection.identity, basePk)) checkRef(errors, resolver, `${base}.projection.identity`, ref);
|
|
185
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Prove that a single-column identity (`column`, or `base_pk` with an
|
|
154
|
+
* explicit column) is actually unique.
|
|
155
|
+
*
|
|
156
|
+
* `coversUniqueKey` fails closed on absent metadata, same as every other
|
|
157
|
+
* check in this file: a column not provably covered by a unique key is
|
|
158
|
+
* rejected, never assumed safe.
|
|
159
|
+
*/
|
|
160
|
+
function checkSingleColumnIdentity(errors, resolver, path, ref) {
|
|
161
|
+
const table = resolver.tableFor(ref.table);
|
|
162
|
+
if (!table) return;
|
|
163
|
+
if (!require_schema_meta.coversUniqueKey(table, [ref.column])) errors.push({
|
|
164
|
+
path,
|
|
165
|
+
message: `identity column "${ref.table}.${ref.column}" is not covered by a unique key of "${table.name}" — pick a column with a unique constraint, or use a composite identity that is`
|
|
166
|
+
});
|
|
167
|
+
}
|
|
186
168
|
function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
187
169
|
const path = `${base}.projection.identity`;
|
|
188
|
-
const pk = primaryKeyColumns(baseTable);
|
|
189
|
-
|
|
170
|
+
const pk = require_schema_meta.primaryKeyColumns(baseTable);
|
|
171
|
+
const { identity } = projection;
|
|
172
|
+
if (identity.kind === "base_pk" && !identity.column) {
|
|
190
173
|
if (pk.length === 0) {
|
|
191
174
|
errors.push({
|
|
192
175
|
path,
|
|
@@ -201,11 +184,23 @@ function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
|
201
184
|
});
|
|
202
185
|
return;
|
|
203
186
|
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (identity.kind === "base_pk" && identity.column) {
|
|
190
|
+
checkSingleColumnIdentity(errors, resolver, path, {
|
|
191
|
+
table: require_projection.BASE_REF,
|
|
192
|
+
column: identity.column
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
204
195
|
}
|
|
205
|
-
if (
|
|
206
|
-
|
|
196
|
+
if (identity.kind === "column") {
|
|
197
|
+
checkSingleColumnIdentity(errors, resolver, path, identity.from);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (identity.kind !== "composite") return;
|
|
201
|
+
for (const part of identity.parts) {
|
|
207
202
|
const table = resolver.tableFor(part.table);
|
|
208
|
-
const meta = table && columnMetaFor(table, part.column);
|
|
203
|
+
const meta = table && require_schema_meta.columnMetaFor(table, part.column);
|
|
209
204
|
if (!meta) continue;
|
|
210
205
|
if (meta.nullable !== false) errors.push({
|
|
211
206
|
path,
|
|
@@ -239,12 +234,70 @@ function checkFanOut(errors, projection, resolver, base, basePk) {
|
|
|
239
234
|
});
|
|
240
235
|
return;
|
|
241
236
|
}
|
|
242
|
-
if (!coversUniqueKey(joinTable, require_projection.identityRefs(projection.identity, basePk).filter((ref) => ref.table === join.key).map((ref) => ref.column))) errors.push({
|
|
237
|
+
if (!require_schema_meta.coversUniqueKey(joinTable, require_projection.identityRefs(projection.identity, basePk).filter((ref) => ref.table === join.key).map((ref) => ref.column))) errors.push({
|
|
243
238
|
path: identityPath,
|
|
244
239
|
message: `identity does not uniquely identify a row of "${joinTable.name}", which fans out — include a unique key of that table`
|
|
245
240
|
});
|
|
246
241
|
}
|
|
247
242
|
/**
|
|
243
|
+
* One error naming a filter column that is not on the projection's base table,
|
|
244
|
+
* and the base table it would have to be on.
|
|
245
|
+
*/
|
|
246
|
+
function columnOnBase(column, baseTable, path) {
|
|
247
|
+
if (baseTable?.columns.includes(column)) return [];
|
|
248
|
+
return [{
|
|
249
|
+
path,
|
|
250
|
+
message: `filter column "${column}" does not exist on "${baseTable?.name ?? "the base table"}" — a derived object is filtered by its base table's columns only, so name one of those, or move the condition into a join's \`on\``
|
|
251
|
+
}];
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* A derived object's filter is evaluated in the cloud against pushed rows, so
|
|
255
|
+
* it can only name columns of the base table.
|
|
256
|
+
*
|
|
257
|
+
* Every rejected shape here fails *silently* today, which is the whole reason
|
|
258
|
+
* the check exists — a filter that cannot be honoured must error, never quietly
|
|
259
|
+
* return the wrong rows. The three shapes fail in different directions:
|
|
260
|
+
*
|
|
261
|
+
* - `$raw` is opaque SQL meant for the embedded model. The cloud path cannot
|
|
262
|
+
* evaluate it and passes every row, so the filter is ignored and the object
|
|
263
|
+
* syncs UNFILTERED.
|
|
264
|
+
* - A joined-table column resolves to nothing on a pushed row, so under a
|
|
265
|
+
* positive operator (`eq`, `in`) no row matches and the object syncs EMPTY.
|
|
266
|
+
* - The same unresolvable column under a NEGATIVE operator (`ne`, `isNull`,
|
|
267
|
+
* `notIn`) is the dangerous one: the missing cell reads as null, so the
|
|
268
|
+
* predicate is true for every row and a filter meant to narrow the sync
|
|
269
|
+
* ADMITS EVERYTHING. Nobody notices extra rows arriving.
|
|
270
|
+
*
|
|
271
|
+
* Rejecting an unresolvable column closes the last two together.
|
|
272
|
+
*/
|
|
273
|
+
function validateProjectionFilter(filter, projection, schema, base) {
|
|
274
|
+
if (!filter) return [];
|
|
275
|
+
const path = `${base}.filter`;
|
|
276
|
+
if ("$raw" in filter) return [{
|
|
277
|
+
path,
|
|
278
|
+
message: "a $raw filter cannot be evaluated against a derived object's rows — it is opaque SQL, so the cloud passes every row through unfiltered. Express it as column conditions on the base table instead"
|
|
279
|
+
}];
|
|
280
|
+
const baseTable = schema.find((t) => t.name === projection.baseTable);
|
|
281
|
+
const errors = [];
|
|
282
|
+
for (const column of Object.keys(filter)) {
|
|
283
|
+
if (column.includes(".")) {
|
|
284
|
+
const [ref, ...rest] = column.split(".");
|
|
285
|
+
const bare = rest.join(".");
|
|
286
|
+
if (ref !== "base") {
|
|
287
|
+
errors.push({
|
|
288
|
+
path,
|
|
289
|
+
message: `filter column "${column}" names the join "${ref}", but a derived object is filtered by its base table ("${projection.baseTable}") only — filter on a base column, or move the condition into that join's \`on\``
|
|
290
|
+
});
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
errors.push(...columnOnBase(bare, baseTable, path));
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
errors.push(...columnOnBase(column, baseTable, path));
|
|
297
|
+
}
|
|
298
|
+
return errors;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
248
301
|
* Validate a projection against the discovered schema. Returns every error
|
|
249
302
|
* found (not just the first) so a builder UI can show them all at once.
|
|
250
303
|
*/
|
|
@@ -255,7 +308,7 @@ function validateProjection(projection, schema, base) {
|
|
|
255
308
|
if (!checkTablesExist(errors, projection, resolver, base)) return errors;
|
|
256
309
|
const baseTable = resolver.tablesByName.get(projection.baseTable);
|
|
257
310
|
if (!baseTable) return errors;
|
|
258
|
-
const basePk = primaryKeyColumns(baseTable)[0] ?? "id";
|
|
311
|
+
const basePk = require_schema_meta.primaryKeyColumns(baseTable)[0] ?? "id";
|
|
259
312
|
checkRefs(errors, projection, resolver, base, basePk);
|
|
260
313
|
checkIdentitySafety(errors, projection, resolver, base, baseTable);
|
|
261
314
|
checkFanOut(errors, projection, resolver, base, basePk);
|
|
@@ -517,6 +570,15 @@ function customObjectDeclarations(config) {
|
|
|
517
570
|
* Qualified rather than bare: two bindings can pull the same column name from
|
|
518
571
|
* different tables (`cl.full_name` and `adv.full_name`), which a bare column
|
|
519
572
|
* name cannot tell apart.
|
|
573
|
+
*
|
|
574
|
+
* This is the single derivation of that map, and both consumers depend on it
|
|
575
|
+
* emitting ref tokens. `mappingsToEntitiesConfig` populates a projection
|
|
576
|
+
* entity's required `fields`; the cloud ingest path needs the same map because
|
|
577
|
+
* a projection's persisted `crm_entity_mappings.field_map` is empty by design,
|
|
578
|
+
* so `rowToSourceRow` derives the effective field map from the projection
|
|
579
|
+
* instead. The tokens emitted here are exactly how `fromDrizzleProjection` keys
|
|
580
|
+
* the rows it pushes (`drizzle-projection.ts` `refToken`), so the two line up.
|
|
581
|
+
* Deriving this a second time anywhere would let the copies drift.
|
|
520
582
|
*/
|
|
521
583
|
function projectionFields(projection) {
|
|
522
584
|
const fields = {};
|
|
@@ -655,7 +717,7 @@ function validateEntity(v, config, key, entity, schema) {
|
|
|
655
717
|
if (entity.source !== entity.projection.baseTable) v.report(`${base}.source`, `"source" ("${entity.source}") must match "projection.baseTable" ("${entity.projection.baseTable}") — the view compiler reads baseTable and the sync engine reads source, so a mismatch makes them disagree about which table this entity is`);
|
|
656
718
|
for (const error of validateProjection(entity.projection, schema, base)) v.report(error.path, error.message);
|
|
657
719
|
validateReadOnly(v, base, entity);
|
|
658
|
-
|
|
720
|
+
for (const error of validateProjectionFilter(entity.filter, entity.projection, schema, base)) v.report(error.path, error.message);
|
|
659
721
|
return;
|
|
660
722
|
}
|
|
661
723
|
validateFields(v, base, entity);
|
|
@@ -711,18 +773,6 @@ Object.defineProperty(exports, "MATERIALIZING_CONCEPTS", {
|
|
|
711
773
|
return MATERIALIZING_CONCEPTS;
|
|
712
774
|
}
|
|
713
775
|
});
|
|
714
|
-
Object.defineProperty(exports, "columnMetaFor", {
|
|
715
|
-
enumerable: true,
|
|
716
|
-
get: function() {
|
|
717
|
-
return columnMetaFor;
|
|
718
|
-
}
|
|
719
|
-
});
|
|
720
|
-
Object.defineProperty(exports, "coversUniqueKey", {
|
|
721
|
-
enumerable: true,
|
|
722
|
-
get: function() {
|
|
723
|
-
return coversUniqueKey;
|
|
724
|
-
}
|
|
725
|
-
});
|
|
726
776
|
Object.defineProperty(exports, "customObjectDeclarations", {
|
|
727
777
|
enumerable: true,
|
|
728
778
|
get: function() {
|
|
@@ -753,24 +803,12 @@ Object.defineProperty(exports, "filterSchema", {
|
|
|
753
803
|
return filterSchema;
|
|
754
804
|
}
|
|
755
805
|
});
|
|
756
|
-
Object.defineProperty(exports, "hasRichMeta", {
|
|
757
|
-
enumerable: true,
|
|
758
|
-
get: function() {
|
|
759
|
-
return hasRichMeta;
|
|
760
|
-
}
|
|
761
|
-
});
|
|
762
806
|
Object.defineProperty(exports, "joinFansOut", {
|
|
763
807
|
enumerable: true,
|
|
764
808
|
get: function() {
|
|
765
809
|
return joinFansOut;
|
|
766
810
|
}
|
|
767
811
|
});
|
|
768
|
-
Object.defineProperty(exports, "primaryKeyColumns", {
|
|
769
|
-
enumerable: true,
|
|
770
|
-
get: function() {
|
|
771
|
-
return primaryKeyColumns;
|
|
772
|
-
}
|
|
773
|
-
});
|
|
774
812
|
Object.defineProperty(exports, "projectionFields", {
|
|
775
813
|
enumerable: true,
|
|
776
814
|
get: function() {
|
|
@@ -801,3 +839,9 @@ Object.defineProperty(exports, "validateProjection", {
|
|
|
801
839
|
return validateProjection;
|
|
802
840
|
}
|
|
803
841
|
});
|
|
842
|
+
Object.defineProperty(exports, "validateProjectionFilter", {
|
|
843
|
+
enumerable: true,
|
|
844
|
+
get: function() {
|
|
845
|
+
return validateProjectionFilter;
|
|
846
|
+
}
|
|
847
|
+
});
|
|
@@ -16,6 +16,23 @@ import { z } from "zod";
|
|
|
16
16
|
*/
|
|
17
17
|
/** The reserved ref name for the projection's base table. */
|
|
18
18
|
declare const BASE_REF = "base";
|
|
19
|
+
/**
|
|
20
|
+
* Entity keys become view names (`crm_src_<entityKey>`), so keep them slugs.
|
|
21
|
+
*
|
|
22
|
+
* Accepts hyphens as well as underscores, matching `OBJECT_TYPE_SLUG_PATTERN`
|
|
23
|
+
* on the server. Those two were widened apart: the server learned hyphens
|
|
24
|
+
* because the SDK's own `slugify` collapses non-alphanumerics to them and
|
|
25
|
+
* `clivly status` reports that form as canonical — but this pattern was missed,
|
|
26
|
+
* so `clivly status` would tell you to key an entity `client-accounts` and then
|
|
27
|
+
* `defineClivlyConfig` would reject the config it had just asked for. Same bug,
|
|
28
|
+
* opposite half of the product.
|
|
29
|
+
*
|
|
30
|
+
* Widening is the safe direction: every key that was valid before still is, so
|
|
31
|
+
* nothing existing is renamed. Safe at all because neither use is ever a bare
|
|
32
|
+
* SQL identifier — the view name is quoted (`view-compiler.ts`, `quoteIdent`),
|
|
33
|
+
* and so is a join's `key` when it is emitted as an alias.
|
|
34
|
+
*/
|
|
35
|
+
declare const PROJECTION_KEY_PATTERN: RegExp;
|
|
19
36
|
declare const projectionRefSchema: z.ZodObject<{
|
|
20
37
|
table: z.ZodString;
|
|
21
38
|
column: z.ZodString;
|
|
@@ -517,6 +534,15 @@ declare function customObjectDeclarations(config: ClivlyEntitiesConfig): CustomO
|
|
|
517
534
|
* Qualified rather than bare: two bindings can pull the same column name from
|
|
518
535
|
* different tables (`cl.full_name` and `adv.full_name`), which a bare column
|
|
519
536
|
* name cannot tell apart.
|
|
537
|
+
*
|
|
538
|
+
* This is the single derivation of that map, and both consumers depend on it
|
|
539
|
+
* emitting ref tokens. `mappingsToEntitiesConfig` populates a projection
|
|
540
|
+
* entity's required `fields`; the cloud ingest path needs the same map because
|
|
541
|
+
* a projection's persisted `crm_entity_mappings.field_map` is empty by design,
|
|
542
|
+
* so `rowToSourceRow` derives the effective field map from the projection
|
|
543
|
+
* instead. The tokens emitted here are exactly how `fromDrizzleProjection` keys
|
|
544
|
+
* the rows it pushes (`drizzle-projection.ts` `refToken`), so the two line up.
|
|
545
|
+
* Deriving this a second time anywhere would let the copies drift.
|
|
520
546
|
*/
|
|
521
547
|
declare function projectionFields(projection: ProjectionMapping): Record<string, string>;
|
|
522
548
|
declare function defineClivlyConfig(config: ClivlyEntitiesConfigInput, options?: {
|
|
@@ -577,4 +603,4 @@ interface ConfigValidationResult {
|
|
|
577
603
|
*/
|
|
578
604
|
declare function validateEntitiesConfig(config: ClivlyEntitiesConfig, schema: DiscoveredSchemaTable[]): ConfigValidationResult;
|
|
579
605
|
//#endregion
|
|
580
|
-
export { BASE_REF as A, defineClivlyConfig as C, relationshipSchema as D, projectionFields as E,
|
|
606
|
+
export { BASE_REF as A, defineClivlyConfig as C, relationshipSchema as D, projectionFields as E, ProjectionMapping as F, ProjectionRef as I, identityRefs as L, ProjectionFieldBinding as M, ProjectionIdentity as N, relationshipsSchema as O, ProjectionJoin as P, projectionSchema as R, customObjectDeclarations as S, filterSchema as T, MATERIALIZING_CONCEPTS as _, ClivlyEntitiesConfig as a, RelationshipVia as b, ClivlyEntityConfigInput as c, CrmConcept as d, CustomObjectDeclaration as f, FilterValue as g, FilterExpr as h, ClivlyConfigError as i, PROJECTION_KEY_PATTERN as j, validateEntitiesConfig as k, ConfigValidationError as l, DiscoveredSchemaTable as m, CRM_CONCEPTS as n, ClivlyEntitiesConfigInput as o, DiscoveredColumnMeta as p, CUSTOM_CONCEPT as r, ClivlyEntityConfig as s, CANONICAL_FIELDS as t, ConfigValidationResult as u, MaterializingConcept as v, entitiesConfigSchema as w, RelationshipsMap as x, RelationshipSpec as y };
|
|
@@ -16,6 +16,23 @@ import { z } from "zod";
|
|
|
16
16
|
*/
|
|
17
17
|
/** The reserved ref name for the projection's base table. */
|
|
18
18
|
declare const BASE_REF = "base";
|
|
19
|
+
/**
|
|
20
|
+
* Entity keys become view names (`crm_src_<entityKey>`), so keep them slugs.
|
|
21
|
+
*
|
|
22
|
+
* Accepts hyphens as well as underscores, matching `OBJECT_TYPE_SLUG_PATTERN`
|
|
23
|
+
* on the server. Those two were widened apart: the server learned hyphens
|
|
24
|
+
* because the SDK's own `slugify` collapses non-alphanumerics to them and
|
|
25
|
+
* `clivly status` reports that form as canonical — but this pattern was missed,
|
|
26
|
+
* so `clivly status` would tell you to key an entity `client-accounts` and then
|
|
27
|
+
* `defineClivlyConfig` would reject the config it had just asked for. Same bug,
|
|
28
|
+
* opposite half of the product.
|
|
29
|
+
*
|
|
30
|
+
* Widening is the safe direction: every key that was valid before still is, so
|
|
31
|
+
* nothing existing is renamed. Safe at all because neither use is ever a bare
|
|
32
|
+
* SQL identifier — the view name is quoted (`view-compiler.ts`, `quoteIdent`),
|
|
33
|
+
* and so is a join's `key` when it is emitted as an alias.
|
|
34
|
+
*/
|
|
35
|
+
declare const PROJECTION_KEY_PATTERN: RegExp;
|
|
19
36
|
declare const projectionRefSchema: z.ZodObject<{
|
|
20
37
|
table: z.ZodString;
|
|
21
38
|
column: z.ZodString;
|
|
@@ -517,6 +534,15 @@ declare function customObjectDeclarations(config: ClivlyEntitiesConfig): CustomO
|
|
|
517
534
|
* Qualified rather than bare: two bindings can pull the same column name from
|
|
518
535
|
* different tables (`cl.full_name` and `adv.full_name`), which a bare column
|
|
519
536
|
* name cannot tell apart.
|
|
537
|
+
*
|
|
538
|
+
* This is the single derivation of that map, and both consumers depend on it
|
|
539
|
+
* emitting ref tokens. `mappingsToEntitiesConfig` populates a projection
|
|
540
|
+
* entity's required `fields`; the cloud ingest path needs the same map because
|
|
541
|
+
* a projection's persisted `crm_entity_mappings.field_map` is empty by design,
|
|
542
|
+
* so `rowToSourceRow` derives the effective field map from the projection
|
|
543
|
+
* instead. The tokens emitted here are exactly how `fromDrizzleProjection` keys
|
|
544
|
+
* the rows it pushes (`drizzle-projection.ts` `refToken`), so the two line up.
|
|
545
|
+
* Deriving this a second time anywhere would let the copies drift.
|
|
520
546
|
*/
|
|
521
547
|
declare function projectionFields(projection: ProjectionMapping): Record<string, string>;
|
|
522
548
|
declare function defineClivlyConfig(config: ClivlyEntitiesConfigInput, options?: {
|
|
@@ -577,4 +603,4 @@ interface ConfigValidationResult {
|
|
|
577
603
|
*/
|
|
578
604
|
declare function validateEntitiesConfig(config: ClivlyEntitiesConfig, schema: DiscoveredSchemaTable[]): ConfigValidationResult;
|
|
579
605
|
//#endregion
|
|
580
|
-
export { BASE_REF as A, defineClivlyConfig as C, relationshipSchema as D, projectionFields as E,
|
|
606
|
+
export { BASE_REF as A, defineClivlyConfig as C, relationshipSchema as D, projectionFields as E, ProjectionMapping as F, ProjectionRef as I, identityRefs as L, ProjectionFieldBinding as M, ProjectionIdentity as N, relationshipsSchema as O, ProjectionJoin as P, projectionSchema as R, customObjectDeclarations as S, filterSchema as T, MATERIALIZING_CONCEPTS as _, ClivlyEntitiesConfig as a, RelationshipVia as b, ClivlyEntityConfigInput as c, CrmConcept as d, CustomObjectDeclaration as f, FilterValue as g, FilterExpr as h, ClivlyConfigError as i, PROJECTION_KEY_PATTERN as j, validateEntitiesConfig as k, ConfigValidationError as l, DiscoveredSchemaTable as m, CRM_CONCEPTS as n, ClivlyEntitiesConfigInput as o, DiscoveredColumnMeta as p, CUSTOM_CONCEPT as r, ClivlyEntityConfig as s, CANONICAL_FIELDS as t, ConfigValidationResult as u, MaterializingConcept as v, entitiesConfigSchema as w, RelationshipsMap as x, RelationshipSpec as y };
|
|
@@ -1,40 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as projectionSchema, r as identityRefs, t as BASE_REF } from "./projection-SCNrLJMV.mjs";
|
|
2
|
+
import { i as primaryKeyColumns, n as coversUniqueKey, r as hasRichMeta, t as columnMetaFor } from "./schema-meta-Ac7938mT.mjs";
|
|
2
3
|
import { z } from "zod";
|
|
3
|
-
//#region src/schema-meta.ts
|
|
4
|
-
/**
|
|
5
|
-
* Accessors over a discovered table's schema-v2 metadata.
|
|
6
|
-
*
|
|
7
|
-
* Every helper treats *absent* metadata as **unknown**, never as a negative
|
|
8
|
-
* fact. `coversUniqueKey` returning false on a v1 payload is what makes
|
|
9
|
-
* projection validation fail closed: we cannot prove a join is many-to-one, so
|
|
10
|
-
* we refuse to compile it rather than guess.
|
|
11
|
-
*/
|
|
12
|
-
function columnMetaFor(table, column) {
|
|
13
|
-
return table.columnsMeta?.find((c) => c.name === column);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Every primary-key column. More than one entry means a composite PK, which is
|
|
17
|
-
* why `base_pk` identity is rejected for such tables.
|
|
18
|
-
*/
|
|
19
|
-
function primaryKeyColumns(table) {
|
|
20
|
-
return (table.columnsMeta ?? []).filter((c) => c.isPrimaryKey).map((c) => c.name);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Do `columns` contain every column of at least one unique key on `table`?
|
|
24
|
-
*
|
|
25
|
-
* This is the fan-out test. A join whose ON columns cover a unique key of the
|
|
26
|
-
* joined table matches at most one row per base row; one that does not can
|
|
27
|
-
* match many.
|
|
28
|
-
*/
|
|
29
|
-
function coversUniqueKey(table, columns) {
|
|
30
|
-
const present = new Set(columns);
|
|
31
|
-
return (table.uniqueConstraints ?? []).some((key) => key.length > 0 && key.every((column) => present.has(column)));
|
|
32
|
-
}
|
|
33
|
-
/** Is this table's payload rich enough to validate a projection against? */
|
|
34
|
-
function hasRichMeta(table) {
|
|
35
|
-
return Boolean(table.columnsMeta && table.uniqueConstraints);
|
|
36
|
-
}
|
|
37
|
-
//#endregion
|
|
38
4
|
//#region src/projection-cardinality.ts
|
|
39
5
|
/**
|
|
40
6
|
* Cardinality analysis for projection joins.
|
|
@@ -138,7 +104,7 @@ function checkTablesExist(errors, projection, resolver, base) {
|
|
|
138
104
|
if (!hasRichMeta(table)) {
|
|
139
105
|
errors.push({
|
|
140
106
|
path,
|
|
141
|
-
message: `table "${name}"
|
|
107
|
+
message: `table "${name}" was discovered without column types or unique keys, which a projection needs to prove its id is unique. Redeploy or restart the app so it re-reports its schema (or run \`clivly push-schema\`); if the table is declared by hand as \`{ name, columns }\`, use \`discoverFromDrizzle\` so keys and types come with it.`
|
|
142
108
|
});
|
|
143
109
|
ok = false;
|
|
144
110
|
}
|
|
@@ -183,10 +149,27 @@ function checkRefs(errors, projection, resolver, base, basePk) {
|
|
|
183
149
|
for (const [field, binding] of Object.entries(projection.fieldBindings)) checkRef(errors, resolver, `${base}.projection.fieldBindings.${field}`, binding.from);
|
|
184
150
|
for (const ref of identityRefs(projection.identity, basePk)) checkRef(errors, resolver, `${base}.projection.identity`, ref);
|
|
185
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Prove that a single-column identity (`column`, or `base_pk` with an
|
|
154
|
+
* explicit column) is actually unique.
|
|
155
|
+
*
|
|
156
|
+
* `coversUniqueKey` fails closed on absent metadata, same as every other
|
|
157
|
+
* check in this file: a column not provably covered by a unique key is
|
|
158
|
+
* rejected, never assumed safe.
|
|
159
|
+
*/
|
|
160
|
+
function checkSingleColumnIdentity(errors, resolver, path, ref) {
|
|
161
|
+
const table = resolver.tableFor(ref.table);
|
|
162
|
+
if (!table) return;
|
|
163
|
+
if (!coversUniqueKey(table, [ref.column])) errors.push({
|
|
164
|
+
path,
|
|
165
|
+
message: `identity column "${ref.table}.${ref.column}" is not covered by a unique key of "${table.name}" — pick a column with a unique constraint, or use a composite identity that is`
|
|
166
|
+
});
|
|
167
|
+
}
|
|
186
168
|
function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
187
169
|
const path = `${base}.projection.identity`;
|
|
188
170
|
const pk = primaryKeyColumns(baseTable);
|
|
189
|
-
|
|
171
|
+
const { identity } = projection;
|
|
172
|
+
if (identity.kind === "base_pk" && !identity.column) {
|
|
190
173
|
if (pk.length === 0) {
|
|
191
174
|
errors.push({
|
|
192
175
|
path,
|
|
@@ -201,9 +184,21 @@ function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
|
201
184
|
});
|
|
202
185
|
return;
|
|
203
186
|
}
|
|
187
|
+
return;
|
|
204
188
|
}
|
|
205
|
-
if (
|
|
206
|
-
|
|
189
|
+
if (identity.kind === "base_pk" && identity.column) {
|
|
190
|
+
checkSingleColumnIdentity(errors, resolver, path, {
|
|
191
|
+
table: BASE_REF,
|
|
192
|
+
column: identity.column
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (identity.kind === "column") {
|
|
197
|
+
checkSingleColumnIdentity(errors, resolver, path, identity.from);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (identity.kind !== "composite") return;
|
|
201
|
+
for (const part of identity.parts) {
|
|
207
202
|
const table = resolver.tableFor(part.table);
|
|
208
203
|
const meta = table && columnMetaFor(table, part.column);
|
|
209
204
|
if (!meta) continue;
|
|
@@ -245,6 +240,64 @@ function checkFanOut(errors, projection, resolver, base, basePk) {
|
|
|
245
240
|
});
|
|
246
241
|
}
|
|
247
242
|
/**
|
|
243
|
+
* One error naming a filter column that is not on the projection's base table,
|
|
244
|
+
* and the base table it would have to be on.
|
|
245
|
+
*/
|
|
246
|
+
function columnOnBase(column, baseTable, path) {
|
|
247
|
+
if (baseTable?.columns.includes(column)) return [];
|
|
248
|
+
return [{
|
|
249
|
+
path,
|
|
250
|
+
message: `filter column "${column}" does not exist on "${baseTable?.name ?? "the base table"}" — a derived object is filtered by its base table's columns only, so name one of those, or move the condition into a join's \`on\``
|
|
251
|
+
}];
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* A derived object's filter is evaluated in the cloud against pushed rows, so
|
|
255
|
+
* it can only name columns of the base table.
|
|
256
|
+
*
|
|
257
|
+
* Every rejected shape here fails *silently* today, which is the whole reason
|
|
258
|
+
* the check exists — a filter that cannot be honoured must error, never quietly
|
|
259
|
+
* return the wrong rows. The three shapes fail in different directions:
|
|
260
|
+
*
|
|
261
|
+
* - `$raw` is opaque SQL meant for the embedded model. The cloud path cannot
|
|
262
|
+
* evaluate it and passes every row, so the filter is ignored and the object
|
|
263
|
+
* syncs UNFILTERED.
|
|
264
|
+
* - A joined-table column resolves to nothing on a pushed row, so under a
|
|
265
|
+
* positive operator (`eq`, `in`) no row matches and the object syncs EMPTY.
|
|
266
|
+
* - The same unresolvable column under a NEGATIVE operator (`ne`, `isNull`,
|
|
267
|
+
* `notIn`) is the dangerous one: the missing cell reads as null, so the
|
|
268
|
+
* predicate is true for every row and a filter meant to narrow the sync
|
|
269
|
+
* ADMITS EVERYTHING. Nobody notices extra rows arriving.
|
|
270
|
+
*
|
|
271
|
+
* Rejecting an unresolvable column closes the last two together.
|
|
272
|
+
*/
|
|
273
|
+
function validateProjectionFilter(filter, projection, schema, base) {
|
|
274
|
+
if (!filter) return [];
|
|
275
|
+
const path = `${base}.filter`;
|
|
276
|
+
if ("$raw" in filter) return [{
|
|
277
|
+
path,
|
|
278
|
+
message: "a $raw filter cannot be evaluated against a derived object's rows — it is opaque SQL, so the cloud passes every row through unfiltered. Express it as column conditions on the base table instead"
|
|
279
|
+
}];
|
|
280
|
+
const baseTable = schema.find((t) => t.name === projection.baseTable);
|
|
281
|
+
const errors = [];
|
|
282
|
+
for (const column of Object.keys(filter)) {
|
|
283
|
+
if (column.includes(".")) {
|
|
284
|
+
const [ref, ...rest] = column.split(".");
|
|
285
|
+
const bare = rest.join(".");
|
|
286
|
+
if (ref !== "base") {
|
|
287
|
+
errors.push({
|
|
288
|
+
path,
|
|
289
|
+
message: `filter column "${column}" names the join "${ref}", but a derived object is filtered by its base table ("${projection.baseTable}") only — filter on a base column, or move the condition into that join's \`on\``
|
|
290
|
+
});
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
errors.push(...columnOnBase(bare, baseTable, path));
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
errors.push(...columnOnBase(column, baseTable, path));
|
|
297
|
+
}
|
|
298
|
+
return errors;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
248
301
|
* Validate a projection against the discovered schema. Returns every error
|
|
249
302
|
* found (not just the first) so a builder UI can show them all at once.
|
|
250
303
|
*/
|
|
@@ -517,6 +570,15 @@ function customObjectDeclarations(config) {
|
|
|
517
570
|
* Qualified rather than bare: two bindings can pull the same column name from
|
|
518
571
|
* different tables (`cl.full_name` and `adv.full_name`), which a bare column
|
|
519
572
|
* name cannot tell apart.
|
|
573
|
+
*
|
|
574
|
+
* This is the single derivation of that map, and both consumers depend on it
|
|
575
|
+
* emitting ref tokens. `mappingsToEntitiesConfig` populates a projection
|
|
576
|
+
* entity's required `fields`; the cloud ingest path needs the same map because
|
|
577
|
+
* a projection's persisted `crm_entity_mappings.field_map` is empty by design,
|
|
578
|
+
* so `rowToSourceRow` derives the effective field map from the projection
|
|
579
|
+
* instead. The tokens emitted here are exactly how `fromDrizzleProjection` keys
|
|
580
|
+
* the rows it pushes (`drizzle-projection.ts` `refToken`), so the two line up.
|
|
581
|
+
* Deriving this a second time anywhere would let the copies drift.
|
|
520
582
|
*/
|
|
521
583
|
function projectionFields(projection) {
|
|
522
584
|
const fields = {};
|
|
@@ -655,7 +717,7 @@ function validateEntity(v, config, key, entity, schema) {
|
|
|
655
717
|
if (entity.source !== entity.projection.baseTable) v.report(`${base}.source`, `"source" ("${entity.source}") must match "projection.baseTable" ("${entity.projection.baseTable}") — the view compiler reads baseTable and the sync engine reads source, so a mismatch makes them disagree about which table this entity is`);
|
|
656
718
|
for (const error of validateProjection(entity.projection, schema, base)) v.report(error.path, error.message);
|
|
657
719
|
validateReadOnly(v, base, entity);
|
|
658
|
-
|
|
720
|
+
for (const error of validateProjectionFilter(entity.filter, entity.projection, schema, base)) v.report(error.path, error.message);
|
|
659
721
|
return;
|
|
660
722
|
}
|
|
661
723
|
validateFields(v, base, entity);
|
|
@@ -681,4 +743,4 @@ function validateEntitiesConfig(config, schema) {
|
|
|
681
743
|
};
|
|
682
744
|
}
|
|
683
745
|
//#endregion
|
|
684
|
-
export {
|
|
746
|
+
export { joinFansOut as _, MATERIALIZING_CONCEPTS as a, entitiesConfigSchema as c, relationshipSchema as d, relationshipsSchema as f, fanOutJoins as g, validateProjectionFilter as h, ClivlyConfigError as i, filterSchema as l, validateProjection as m, CRM_CONCEPTS as n, customObjectDeclarations as o, validateEntitiesConfig as p, CUSTOM_CONCEPT as r, defineClivlyConfig as s, CANONICAL_FIELDS as t, projectionFields as u };
|
package/dist/entity-config.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_entity_config = require("./entity-config-
|
|
2
|
+
const require_entity_config = require("./entity-config-C5jBmG2C.cjs");
|
|
3
3
|
exports.CANONICAL_FIELDS = require_entity_config.CANONICAL_FIELDS;
|
|
4
4
|
exports.CRM_CONCEPTS = require_entity_config.CRM_CONCEPTS;
|
|
5
5
|
exports.CUSTOM_CONCEPT = require_entity_config.CUSTOM_CONCEPT;
|
package/dist/entity-config.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as defineClivlyConfig, D as relationshipSchema, E as projectionFields, O as relationshipsSchema, S as customObjectDeclarations, T as filterSchema, _ as MATERIALIZING_CONCEPTS, a as ClivlyEntitiesConfig, b as RelationshipVia, c as ClivlyEntityConfigInput, d as CrmConcept, f as CustomObjectDeclaration, g as FilterValue, h as FilterExpr, i as ClivlyConfigError, k as validateEntitiesConfig, l as ConfigValidationError, m as DiscoveredSchemaTable, n as CRM_CONCEPTS, o as ClivlyEntitiesConfigInput, p as DiscoveredColumnMeta, r as CUSTOM_CONCEPT, s as ClivlyEntityConfig, t as CANONICAL_FIELDS, u as ConfigValidationResult, v as MaterializingConcept, w as entitiesConfigSchema, x as RelationshipsMap, y as RelationshipSpec } from "./entity-config-
|
|
1
|
+
import { C as defineClivlyConfig, D as relationshipSchema, E as projectionFields, O as relationshipsSchema, S as customObjectDeclarations, T as filterSchema, _ as MATERIALIZING_CONCEPTS, a as ClivlyEntitiesConfig, b as RelationshipVia, c as ClivlyEntityConfigInput, d as CrmConcept, f as CustomObjectDeclaration, g as FilterValue, h as FilterExpr, i as ClivlyConfigError, k as validateEntitiesConfig, l as ConfigValidationError, m as DiscoveredSchemaTable, n as CRM_CONCEPTS, o as ClivlyEntitiesConfigInput, p as DiscoveredColumnMeta, r as CUSTOM_CONCEPT, s as ClivlyEntityConfig, t as CANONICAL_FIELDS, u as ConfigValidationResult, v as MaterializingConcept, w as entitiesConfigSchema, x as RelationshipsMap, y as RelationshipSpec } from "./entity-config-DCa785OE.cjs";
|
|
2
2
|
export { CANONICAL_FIELDS, CRM_CONCEPTS, CUSTOM_CONCEPT, ClivlyConfigError, ClivlyEntitiesConfig, ClivlyEntitiesConfigInput, ClivlyEntityConfig, ClivlyEntityConfigInput, ConfigValidationError, ConfigValidationResult, CrmConcept, CustomObjectDeclaration, DiscoveredColumnMeta, DiscoveredSchemaTable, FilterExpr, FilterValue, MATERIALIZING_CONCEPTS, MaterializingConcept, RelationshipSpec, RelationshipVia, RelationshipsMap, customObjectDeclarations, defineClivlyConfig, entitiesConfigSchema, filterSchema, projectionFields, relationshipSchema, relationshipsSchema, validateEntitiesConfig };
|
package/dist/entity-config.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as defineClivlyConfig, D as relationshipSchema, E as projectionFields, O as relationshipsSchema, S as customObjectDeclarations, T as filterSchema, _ as MATERIALIZING_CONCEPTS, a as ClivlyEntitiesConfig, b as RelationshipVia, c as ClivlyEntityConfigInput, d as CrmConcept, f as CustomObjectDeclaration, g as FilterValue, h as FilterExpr, i as ClivlyConfigError, k as validateEntitiesConfig, l as ConfigValidationError, m as DiscoveredSchemaTable, n as CRM_CONCEPTS, o as ClivlyEntitiesConfigInput, p as DiscoveredColumnMeta, r as CUSTOM_CONCEPT, s as ClivlyEntityConfig, t as CANONICAL_FIELDS, u as ConfigValidationResult, v as MaterializingConcept, w as entitiesConfigSchema, x as RelationshipsMap, y as RelationshipSpec } from "./entity-config-
|
|
1
|
+
import { C as defineClivlyConfig, D as relationshipSchema, E as projectionFields, O as relationshipsSchema, S as customObjectDeclarations, T as filterSchema, _ as MATERIALIZING_CONCEPTS, a as ClivlyEntitiesConfig, b as RelationshipVia, c as ClivlyEntityConfigInput, d as CrmConcept, f as CustomObjectDeclaration, g as FilterValue, h as FilterExpr, i as ClivlyConfigError, k as validateEntitiesConfig, l as ConfigValidationError, m as DiscoveredSchemaTable, n as CRM_CONCEPTS, o as ClivlyEntitiesConfigInput, p as DiscoveredColumnMeta, r as CUSTOM_CONCEPT, s as ClivlyEntityConfig, t as CANONICAL_FIELDS, u as ConfigValidationResult, v as MaterializingConcept, w as entitiesConfigSchema, x as RelationshipsMap, y as RelationshipSpec } from "./entity-config-DCa785OE.mjs";
|
|
2
2
|
export { CANONICAL_FIELDS, CRM_CONCEPTS, CUSTOM_CONCEPT, ClivlyConfigError, ClivlyEntitiesConfig, ClivlyEntitiesConfigInput, ClivlyEntityConfig, ClivlyEntityConfigInput, ConfigValidationError, ConfigValidationResult, CrmConcept, CustomObjectDeclaration, DiscoveredColumnMeta, DiscoveredSchemaTable, FilterExpr, FilterValue, MATERIALIZING_CONCEPTS, MaterializingConcept, RelationshipSpec, RelationshipVia, RelationshipsMap, customObjectDeclarations, defineClivlyConfig, entitiesConfigSchema, filterSchema, projectionFields, relationshipSchema, relationshipsSchema, validateEntitiesConfig };
|
package/dist/entity-config.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as MATERIALIZING_CONCEPTS, c as entitiesConfigSchema, d as relationshipSchema, f as relationshipsSchema, i as ClivlyConfigError, l as filterSchema, n as CRM_CONCEPTS, o as customObjectDeclarations, p as validateEntitiesConfig, r as CUSTOM_CONCEPT, s as defineClivlyConfig, t as CANONICAL_FIELDS, u as projectionFields } from "./entity-config-
|
|
1
|
+
import { a as MATERIALIZING_CONCEPTS, c as entitiesConfigSchema, d as relationshipSchema, f as relationshipsSchema, i as ClivlyConfigError, l as filterSchema, n as CRM_CONCEPTS, o as customObjectDeclarations, p as validateEntitiesConfig, r as CUSTOM_CONCEPT, s as defineClivlyConfig, t as CANONICAL_FIELDS, u as projectionFields } from "./entity-config-DpHPMFIS.mjs";
|
|
2
2
|
export { CANONICAL_FIELDS, CRM_CONCEPTS, CUSTOM_CONCEPT, ClivlyConfigError, MATERIALIZING_CONCEPTS, customObjectDeclarations, defineClivlyConfig, entitiesConfigSchema, filterSchema, projectionFields, relationshipSchema, relationshipsSchema, validateEntitiesConfig };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_entity_config = require("./entity-config-
|
|
2
|
+
const require_entity_config = require("./entity-config-C5jBmG2C.cjs");
|
|
3
3
|
const require_mapping_score = require("./mapping-score.cjs");
|
|
4
4
|
//#region src/entity-heuristics.ts
|
|
5
5
|
const TARGET_CONCEPTS = [
|
|
@@ -251,6 +251,38 @@ function matchesConcept(tableName, concept) {
|
|
|
251
251
|
if (entry.names.some((name) => candidates.some((candidate) => candidate.includes(name)))) return { exact: false };
|
|
252
252
|
return null;
|
|
253
253
|
}
|
|
254
|
+
const SENSITIVE_COLUMNS = /* @__PURE__ */ new Set([
|
|
255
|
+
"password",
|
|
256
|
+
"password_hash",
|
|
257
|
+
"passwordhash",
|
|
258
|
+
"access_token",
|
|
259
|
+
"accesstoken",
|
|
260
|
+
"api_key",
|
|
261
|
+
"apikey",
|
|
262
|
+
"client_secret",
|
|
263
|
+
"clientsecret",
|
|
264
|
+
"encrypted_password",
|
|
265
|
+
"encryptedpassword",
|
|
266
|
+
"refresh_token",
|
|
267
|
+
"refreshtoken",
|
|
268
|
+
"id_token",
|
|
269
|
+
"idtoken",
|
|
270
|
+
"password_digest",
|
|
271
|
+
"passworddigest",
|
|
272
|
+
"private_key",
|
|
273
|
+
"privatekey",
|
|
274
|
+
"secret",
|
|
275
|
+
"session_token",
|
|
276
|
+
"sessiontoken"
|
|
277
|
+
]);
|
|
278
|
+
/**
|
|
279
|
+
* Does this table carry credentials? Such tables are withheld from suggestions
|
|
280
|
+
* and from bulk apply, so one click can never map an auth table into the CRM.
|
|
281
|
+
* They stay manually selectable — an explicit, informed choice is legitimate.
|
|
282
|
+
*/
|
|
283
|
+
function hasSensitiveColumns(columns) {
|
|
284
|
+
return columns.some((column) => SENSITIVE_COLUMNS.has(column.toLowerCase().replaceAll("-", "_")));
|
|
285
|
+
}
|
|
254
286
|
function metaFor(table, column) {
|
|
255
287
|
return table.columnsMeta?.find((c) => c.name === column);
|
|
256
288
|
}
|
|
@@ -463,6 +495,7 @@ function toFieldMap(scored) {
|
|
|
463
495
|
}
|
|
464
496
|
//#endregion
|
|
465
497
|
exports.TARGET_CONCEPTS = TARGET_CONCEPTS;
|
|
498
|
+
exports.hasSensitiveColumns = hasSensitiveColumns;
|
|
466
499
|
exports.matchesConcept = matchesConcept;
|
|
467
500
|
exports.requiredFieldsFor = requiredFieldsFor;
|
|
468
501
|
exports.scoreConcepts = scoreConcepts;
|