@clivly/core 0.9.5 → 0.9.7
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 +23 -2
- package/dist/drizzle.cjs +9 -1
- package/dist/drizzle.d.cts +3 -1
- package/dist/drizzle.d.mts +3 -1
- package/dist/drizzle.mjs +9 -1
- package/dist/{entity-config-ZSv3XuW_.cjs → entity-config-25v_udeY.cjs} +34 -5
- package/dist/{entity-config-Dt_KfgRl.mjs → entity-config-DwGXbUo_.mjs} +34 -5
- package/dist/entity-config.cjs +1 -1
- package/dist/entity-config.mjs +1 -1
- package/dist/entity-heuristics.cjs +1 -1
- package/dist/entity-heuristics.mjs +1 -1
- package/dist/index.cjs +162 -12
- package/dist/index.d.cts +82 -2
- package/dist/index.d.mts +82 -2
- package/dist/index.mjs +157 -13
- package/dist/mappings-config.cjs +1 -1
- package/dist/mappings-config.mjs +1 -1
- package/dist/{projection-SCNrLJMV.mjs → projection-BXw_iiw5.mjs} +1 -1
- package/dist/{projection-DFgCoyZl.cjs → projection-OJYCYmS_.cjs} +6 -0
- package/dist/sync-engine.cjs +1 -1
- package/dist/sync-engine.mjs +1 -1
- package/dist/{view-compiler-DTGSwTcz.cjs → view-compiler-D30HNRnA.cjs} +51 -1
- package/dist/{view-compiler-DjzRXiAJ.mjs → view-compiler-u3GGRgnv.mjs} +34 -2
- package/dist/view-compiler.cjs +1 -1
- package/dist/view-compiler.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -168,6 +168,19 @@ Refer to the base table as `base` and each join by its `key`. Validation is
|
|
|
168
168
|
`uniqueConstraints` is rejected rather than assumed safe, because identity
|
|
169
169
|
uniqueness cannot be proven without them.
|
|
170
170
|
|
|
171
|
+
`validateProjection` is also the SDK's guard when it runs an object authored in
|
|
172
|
+
Clivly's dashboard: the definition arrives over the network, and the host
|
|
173
|
+
revalidates it **against the schema the host itself discovered** before any SQL
|
|
174
|
+
is compiled. Because `checkTablesExist` and `checkRefs` bound every table and
|
|
175
|
+
column reference to the schema array you pass in, that call is what limits a
|
|
176
|
+
remote definition to the namespace the developer handed to `discoverFromDrizzle`
|
|
177
|
+
— pass a different schema and you move that boundary.
|
|
178
|
+
|
|
179
|
+
Since `0.9.7`, **every** identity kind must be provably unique, not just
|
|
180
|
+
composite ones: a single-column identity is rejected unless a unique key covers
|
|
181
|
+
it. The identity is what the sync pages by and what decides which rows survive a
|
|
182
|
+
snapshot, so an unproven one can silently skip and then archive real records.
|
|
183
|
+
|
|
171
184
|
### Fan-out
|
|
172
185
|
|
|
173
186
|
A join that does not cover a unique key of the joined table can match many rows
|
|
@@ -188,10 +201,18 @@ every rule is a pure function of draft + schema:
|
|
|
188
201
|
| Helper | Purpose |
|
|
189
202
|
| --- | --- |
|
|
190
203
|
| `emptyProjectionDraft(baseTable)` | A blank draft |
|
|
204
|
+
| `addDraftJoin(draft, joinTable, schema)` | Add a join, keyed uniquely and pre-filled from foreign-key metadata |
|
|
205
|
+
| `removeDraftJoin(draft, joinKey)` | Remove a join **and** every binding and identity that addressed it — left behind they fail validation as "unknown ref table", blaming the field for the join's removal |
|
|
206
|
+
| `draftRefOptions(draft, schema)` | The tables and columns a field or identity may currently read from, in ref order |
|
|
191
207
|
| `suggestJoinConditions(base, joinTable, schema, key)` | Pre-fill a join's `ON` from foreign-key metadata |
|
|
192
208
|
| `draftToProjection(draft)` | `ProjectionMapping`, or `null` while incomplete |
|
|
193
|
-
| `draftErrors(draft, schema)` | Validation errors
|
|
194
|
-
| `describeCardinality(draft, schema)` | Per-join `{ joinKey, fansOut }` for inline hints |
|
|
209
|
+
| `draftErrors(draft, schema)` | Validation errors, one per cause, each anchored to the section that owns it (`projection.entityKey`, `projection.joins`, …) |
|
|
210
|
+
| `describeCardinality(draft, schema)` | Per-join `{ joinKey, fansOut }` for inline hints. Answered from the joins alone, so the warning arrives while the identity is still being chosen |
|
|
211
|
+
|
|
212
|
+
`compileProjectionPreview(projection, limit)` compiles the same object to a
|
|
213
|
+
capped, read-only `SELECT` — used by both sides of the dashboard's preview, so
|
|
214
|
+
an embedded org and a cloud org's host app cannot disagree about what the user
|
|
215
|
+
is being shown.
|
|
195
216
|
|
|
196
217
|
Full guide, including how to choose an identity and the current limits:
|
|
197
218
|
[`docs/guides/derived-objects.md`](https://github.com/amani-joseph/clivly.com/blob/master/docs/guides/derived-objects.md).
|
package/dist/drizzle.cjs
CHANGED
|
@@ -92,7 +92,7 @@ function buildColumnsMeta(table) {
|
|
|
92
92
|
* when you import this entry (`@clivly/core/drizzle`); the rest of `@clivly/core`
|
|
93
93
|
* stays dependency-free.
|
|
94
94
|
*/
|
|
95
|
-
function discoverFromDrizzle(schema) {
|
|
95
|
+
function discoverFromDrizzle(schema, options = {}) {
|
|
96
96
|
const tables = [];
|
|
97
97
|
for (const value of Object.values(schema)) {
|
|
98
98
|
if (!(0, drizzle_orm.is)(value, drizzle_orm.Table)) continue;
|
|
@@ -105,8 +105,16 @@ function discoverFromDrizzle(schema) {
|
|
|
105
105
|
...uniqueConstraints === void 0 ? {} : { uniqueConstraints }
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
+
if (tables.length === 0 && !options.allowEmpty) throw new Error(`discoverFromDrizzle: found no Drizzle tables in the object it was given (${describeKeys(schema)}). Import the schema module's tables directly — \`import * as schema from "./db/schema"\` — rather than a barrel or a default export, which can collapse to \`{ default }\` under ESM/CJS interop. Pass { allowEmpty: true } if an empty schema is genuinely intended.`);
|
|
108
109
|
return tables;
|
|
109
110
|
}
|
|
111
|
+
/** The keys actually present, so the message names what was handed over. */
|
|
112
|
+
function describeKeys(schema) {
|
|
113
|
+
const keys = Object.keys(schema);
|
|
114
|
+
if (keys.length === 0) return "it was empty";
|
|
115
|
+
const shown = keys.slice(0, 5).join(", ");
|
|
116
|
+
return keys.length > 5 ? `keys: ${shown}, +${keys.length - 5} more` : `keys: ${shown}`;
|
|
117
|
+
}
|
|
110
118
|
//#endregion
|
|
111
119
|
exports.buildColumnsMeta = buildColumnsMeta;
|
|
112
120
|
exports.buildUniqueConstraints = buildUniqueConstraints;
|
package/dist/drizzle.d.cts
CHANGED
|
@@ -36,6 +36,8 @@ declare function buildColumnsMeta(table: Table): DiscoveredColumnMeta[];
|
|
|
36
36
|
* when you import this entry (`@clivly/core/drizzle`); the rest of `@clivly/core`
|
|
37
37
|
* stays dependency-free.
|
|
38
38
|
*/
|
|
39
|
-
declare function discoverFromDrizzle(schema: Record<string, unknown
|
|
39
|
+
declare function discoverFromDrizzle(schema: Record<string, unknown>, options?: {
|
|
40
|
+
allowEmpty?: boolean;
|
|
41
|
+
}): DiscoveredSchemaTable[];
|
|
40
42
|
//#endregion
|
|
41
43
|
export { buildColumnsMeta, buildUniqueConstraints, discoverFromDrizzle };
|
package/dist/drizzle.d.mts
CHANGED
|
@@ -36,6 +36,8 @@ declare function buildColumnsMeta(table: Table): DiscoveredColumnMeta[];
|
|
|
36
36
|
* when you import this entry (`@clivly/core/drizzle`); the rest of `@clivly/core`
|
|
37
37
|
* stays dependency-free.
|
|
38
38
|
*/
|
|
39
|
-
declare function discoverFromDrizzle(schema: Record<string, unknown
|
|
39
|
+
declare function discoverFromDrizzle(schema: Record<string, unknown>, options?: {
|
|
40
|
+
allowEmpty?: boolean;
|
|
41
|
+
}): DiscoveredSchemaTable[];
|
|
40
42
|
//#endregion
|
|
41
43
|
export { buildColumnsMeta, buildUniqueConstraints, discoverFromDrizzle };
|
package/dist/drizzle.mjs
CHANGED
|
@@ -91,7 +91,7 @@ function buildColumnsMeta(table) {
|
|
|
91
91
|
* when you import this entry (`@clivly/core/drizzle`); the rest of `@clivly/core`
|
|
92
92
|
* stays dependency-free.
|
|
93
93
|
*/
|
|
94
|
-
function discoverFromDrizzle(schema) {
|
|
94
|
+
function discoverFromDrizzle(schema, options = {}) {
|
|
95
95
|
const tables = [];
|
|
96
96
|
for (const value of Object.values(schema)) {
|
|
97
97
|
if (!is(value, Table)) continue;
|
|
@@ -104,7 +104,15 @@ function discoverFromDrizzle(schema) {
|
|
|
104
104
|
...uniqueConstraints === void 0 ? {} : { uniqueConstraints }
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
|
+
if (tables.length === 0 && !options.allowEmpty) throw new Error(`discoverFromDrizzle: found no Drizzle tables in the object it was given (${describeKeys(schema)}). Import the schema module's tables directly — \`import * as schema from "./db/schema"\` — rather than a barrel or a default export, which can collapse to \`{ default }\` under ESM/CJS interop. Pass { allowEmpty: true } if an empty schema is genuinely intended.`);
|
|
107
108
|
return tables;
|
|
108
109
|
}
|
|
110
|
+
/** The keys actually present, so the message names what was handed over. */
|
|
111
|
+
function describeKeys(schema) {
|
|
112
|
+
const keys = Object.keys(schema);
|
|
113
|
+
if (keys.length === 0) return "it was empty";
|
|
114
|
+
const shown = keys.slice(0, 5).join(", ");
|
|
115
|
+
return keys.length > 5 ? `keys: ${shown}, +${keys.length - 5} more` : `keys: ${shown}`;
|
|
116
|
+
}
|
|
109
117
|
//#endregion
|
|
110
118
|
export { buildColumnsMeta, buildUniqueConstraints, discoverFromDrizzle };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_projection = require("./projection-
|
|
1
|
+
const require_projection = require("./projection-OJYCYmS_.cjs");
|
|
2
2
|
let zod = require("zod");
|
|
3
3
|
//#region src/schema-meta.ts
|
|
4
4
|
/**
|
|
@@ -138,7 +138,7 @@ function checkTablesExist(errors, projection, resolver, base) {
|
|
|
138
138
|
if (!hasRichMeta(table)) {
|
|
139
139
|
errors.push({
|
|
140
140
|
path,
|
|
141
|
-
message: `table "${name}"
|
|
141
|
+
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
142
|
});
|
|
143
143
|
ok = false;
|
|
144
144
|
}
|
|
@@ -183,10 +183,27 @@ function checkRefs(errors, projection, resolver, base, basePk) {
|
|
|
183
183
|
for (const [field, binding] of Object.entries(projection.fieldBindings)) checkRef(errors, resolver, `${base}.projection.fieldBindings.${field}`, binding.from);
|
|
184
184
|
for (const ref of require_projection.identityRefs(projection.identity, basePk)) checkRef(errors, resolver, `${base}.projection.identity`, ref);
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Prove that a single-column identity (`column`, or `base_pk` with an
|
|
188
|
+
* explicit column) is actually unique.
|
|
189
|
+
*
|
|
190
|
+
* `coversUniqueKey` fails closed on absent metadata, same as every other
|
|
191
|
+
* check in this file: a column not provably covered by a unique key is
|
|
192
|
+
* rejected, never assumed safe.
|
|
193
|
+
*/
|
|
194
|
+
function checkSingleColumnIdentity(errors, resolver, path, ref) {
|
|
195
|
+
const table = resolver.tableFor(ref.table);
|
|
196
|
+
if (!table) return;
|
|
197
|
+
if (!coversUniqueKey(table, [ref.column])) errors.push({
|
|
198
|
+
path,
|
|
199
|
+
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`
|
|
200
|
+
});
|
|
201
|
+
}
|
|
186
202
|
function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
187
203
|
const path = `${base}.projection.identity`;
|
|
188
204
|
const pk = primaryKeyColumns(baseTable);
|
|
189
|
-
|
|
205
|
+
const { identity } = projection;
|
|
206
|
+
if (identity.kind === "base_pk" && !identity.column) {
|
|
190
207
|
if (pk.length === 0) {
|
|
191
208
|
errors.push({
|
|
192
209
|
path,
|
|
@@ -201,9 +218,21 @@ function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
|
201
218
|
});
|
|
202
219
|
return;
|
|
203
220
|
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (identity.kind === "base_pk" && identity.column) {
|
|
224
|
+
checkSingleColumnIdentity(errors, resolver, path, {
|
|
225
|
+
table: require_projection.BASE_REF,
|
|
226
|
+
column: identity.column
|
|
227
|
+
});
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (identity.kind === "column") {
|
|
231
|
+
checkSingleColumnIdentity(errors, resolver, path, identity.from);
|
|
232
|
+
return;
|
|
204
233
|
}
|
|
205
|
-
if (
|
|
206
|
-
for (const part of
|
|
234
|
+
if (identity.kind !== "composite") return;
|
|
235
|
+
for (const part of identity.parts) {
|
|
207
236
|
const table = resolver.tableFor(part.table);
|
|
208
237
|
const meta = table && columnMetaFor(table, part.column);
|
|
209
238
|
if (!meta) continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as projectionSchema, i as identityRefs, t as BASE_REF } from "./projection-BXw_iiw5.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region src/schema-meta.ts
|
|
4
4
|
/**
|
|
@@ -138,7 +138,7 @@ function checkTablesExist(errors, projection, resolver, base) {
|
|
|
138
138
|
if (!hasRichMeta(table)) {
|
|
139
139
|
errors.push({
|
|
140
140
|
path,
|
|
141
|
-
message: `table "${name}"
|
|
141
|
+
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
142
|
});
|
|
143
143
|
ok = false;
|
|
144
144
|
}
|
|
@@ -183,10 +183,27 @@ function checkRefs(errors, projection, resolver, base, basePk) {
|
|
|
183
183
|
for (const [field, binding] of Object.entries(projection.fieldBindings)) checkRef(errors, resolver, `${base}.projection.fieldBindings.${field}`, binding.from);
|
|
184
184
|
for (const ref of identityRefs(projection.identity, basePk)) checkRef(errors, resolver, `${base}.projection.identity`, ref);
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Prove that a single-column identity (`column`, or `base_pk` with an
|
|
188
|
+
* explicit column) is actually unique.
|
|
189
|
+
*
|
|
190
|
+
* `coversUniqueKey` fails closed on absent metadata, same as every other
|
|
191
|
+
* check in this file: a column not provably covered by a unique key is
|
|
192
|
+
* rejected, never assumed safe.
|
|
193
|
+
*/
|
|
194
|
+
function checkSingleColumnIdentity(errors, resolver, path, ref) {
|
|
195
|
+
const table = resolver.tableFor(ref.table);
|
|
196
|
+
if (!table) return;
|
|
197
|
+
if (!coversUniqueKey(table, [ref.column])) errors.push({
|
|
198
|
+
path,
|
|
199
|
+
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`
|
|
200
|
+
});
|
|
201
|
+
}
|
|
186
202
|
function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
187
203
|
const path = `${base}.projection.identity`;
|
|
188
204
|
const pk = primaryKeyColumns(baseTable);
|
|
189
|
-
|
|
205
|
+
const { identity } = projection;
|
|
206
|
+
if (identity.kind === "base_pk" && !identity.column) {
|
|
190
207
|
if (pk.length === 0) {
|
|
191
208
|
errors.push({
|
|
192
209
|
path,
|
|
@@ -201,9 +218,21 @@ function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
|
201
218
|
});
|
|
202
219
|
return;
|
|
203
220
|
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (identity.kind === "base_pk" && identity.column) {
|
|
224
|
+
checkSingleColumnIdentity(errors, resolver, path, {
|
|
225
|
+
table: BASE_REF,
|
|
226
|
+
column: identity.column
|
|
227
|
+
});
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (identity.kind === "column") {
|
|
231
|
+
checkSingleColumnIdentity(errors, resolver, path, identity.from);
|
|
232
|
+
return;
|
|
204
233
|
}
|
|
205
|
-
if (
|
|
206
|
-
for (const part of
|
|
234
|
+
if (identity.kind !== "composite") return;
|
|
235
|
+
for (const part of identity.parts) {
|
|
207
236
|
const table = resolver.tableFor(part.table);
|
|
208
237
|
const meta = table && columnMetaFor(table, part.column);
|
|
209
238
|
if (!meta) continue;
|
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-25v_udeY.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.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-DwGXbUo_.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-25v_udeY.cjs");
|
|
3
3
|
const require_mapping_score = require("./mapping-score.cjs");
|
|
4
4
|
//#region src/entity-heuristics.ts
|
|
5
5
|
const TARGET_CONCEPTS = [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as MATERIALIZING_CONCEPTS } from "./entity-config-
|
|
1
|
+
import { a as MATERIALIZING_CONCEPTS } from "./entity-config-DwGXbUo_.mjs";
|
|
2
2
|
import { CONCEPT_TIERS, FIELD_TIERS, RELATIONSHIP_TIERS, W, isAmbiguous, isNumericLike, isTextLike, isTimestampLike, tierFor } from "./mapping-score.mjs";
|
|
3
3
|
//#region src/entity-heuristics.ts
|
|
4
4
|
const TARGET_CONCEPTS = [
|
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_auth_adapter = require("./auth-adapter.cjs");
|
|
3
|
-
const require_projection = require("./projection-
|
|
4
|
-
const require_entity_config = require("./entity-config-
|
|
3
|
+
const require_projection = require("./projection-OJYCYmS_.cjs");
|
|
4
|
+
const require_entity_config = require("./entity-config-25v_udeY.cjs");
|
|
5
5
|
const require_mapping_form = require("./mapping-form.cjs");
|
|
6
6
|
const require_mappings_config = require("./mappings-config.cjs");
|
|
7
|
-
const require_view_compiler = require("./view-compiler-
|
|
7
|
+
const require_view_compiler = require("./view-compiler-D30HNRnA.cjs");
|
|
8
8
|
const require_sync_engine = require("./sync-engine.cjs");
|
|
9
9
|
//#region src/projection-draft.ts
|
|
10
10
|
function emptyProjectionDraft(baseTable) {
|
|
@@ -37,6 +37,78 @@ function suggestJoinConditions(baseTable, joinTable, schema, joinKey) {
|
|
|
37
37
|
});
|
|
38
38
|
return conditions;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Add a join on `joinTable`, keyed and pre-filled.
|
|
42
|
+
*
|
|
43
|
+
* The key is the table's own name where that is free, because it is what the
|
|
44
|
+
* user already calls the table; a collision falls back to `_2`, `_3` and so on.
|
|
45
|
+
* Uniqueness is not cosmetic — the key is the SQL alias and the name every ref
|
|
46
|
+
* addresses the table by, so two joins sharing one would make every ref
|
|
47
|
+
* ambiguous. `base` is reserved for the base table and can never be taken.
|
|
48
|
+
*/
|
|
49
|
+
function addDraftJoin(draft, joinTable, schema) {
|
|
50
|
+
const taken = /* @__PURE__ */ new Set([require_projection.BASE_REF, ...draft.joins.map((j) => j.key)]);
|
|
51
|
+
let key = joinTable;
|
|
52
|
+
let suffix = 2;
|
|
53
|
+
while (taken.has(key)) {
|
|
54
|
+
key = `${joinTable}_${suffix}`;
|
|
55
|
+
suffix += 1;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
...draft,
|
|
59
|
+
joins: [...draft.joins, {
|
|
60
|
+
key,
|
|
61
|
+
table: joinTable,
|
|
62
|
+
type: "left",
|
|
63
|
+
on: suggestJoinConditions(draft.baseTable, joinTable, schema, key)
|
|
64
|
+
}]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Remove a join, and everything that addressed it.
|
|
69
|
+
*
|
|
70
|
+
* Refs to a removed join are refs to a table the projection no longer declares.
|
|
71
|
+
* Left in place they fail validation as "unknown ref table", which blames the
|
|
72
|
+
* field — or, worse, the identity — for the join's removal. Identity falls back
|
|
73
|
+
* to the base primary key, the same default a new draft starts from.
|
|
74
|
+
*/
|
|
75
|
+
function removeDraftJoin(draft, joinKey) {
|
|
76
|
+
const fieldBindings = Object.fromEntries(Object.entries(draft.fieldBindings).filter(([, binding]) => binding.from.table !== joinKey));
|
|
77
|
+
const identityDependsOnJoin = require_projection.identityRefs(draft.identity, "").some((ref) => ref.table === joinKey);
|
|
78
|
+
return {
|
|
79
|
+
...draft,
|
|
80
|
+
joins: draft.joins.filter((join) => join.key !== joinKey),
|
|
81
|
+
fieldBindings,
|
|
82
|
+
identity: identityDependsOnJoin ? { kind: "base_pk" } : draft.identity
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Every table a field or identity may currently read from, in ref order: the
|
|
87
|
+
* base table first, then each join.
|
|
88
|
+
*
|
|
89
|
+
* Returns [] when the base table is not in the discovered schema — there is
|
|
90
|
+
* nothing truthful to offer, and inventing columns would let someone build a
|
|
91
|
+
* projection that cannot compile.
|
|
92
|
+
*/
|
|
93
|
+
function draftRefOptions(draft, schema) {
|
|
94
|
+
const byName = new Map(schema.map((table) => [table.name, table]));
|
|
95
|
+
const base = byName.get(draft.baseTable);
|
|
96
|
+
if (!base) return [];
|
|
97
|
+
const options = [{
|
|
98
|
+
ref: require_projection.BASE_REF,
|
|
99
|
+
label: `${base.name} (${require_projection.BASE_REF})`,
|
|
100
|
+
columns: base.columns
|
|
101
|
+
}];
|
|
102
|
+
for (const join of draft.joins) {
|
|
103
|
+
const table = byName.get(join.table);
|
|
104
|
+
if (table) options.push({
|
|
105
|
+
ref: join.key,
|
|
106
|
+
label: `${table.name} (${join.key})`,
|
|
107
|
+
columns: table.columns
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return options;
|
|
111
|
+
}
|
|
40
112
|
/** A draft becomes a projection once it has a valid key and ≥1 binding. */
|
|
41
113
|
function draftToProjection(draft) {
|
|
42
114
|
if (!require_projection.PROJECTION_KEY_PATTERN.test(draft.entityKey)) return null;
|
|
@@ -51,18 +123,90 @@ function draftToProjection(draft) {
|
|
|
51
123
|
identity: draft.identity
|
|
52
124
|
};
|
|
53
125
|
}
|
|
126
|
+
const PROBE_KEY = "draft";
|
|
127
|
+
/**
|
|
128
|
+
* A draft rendered as a projection so the semantic checks can run against it
|
|
129
|
+
* while it is being written, not only once it is finished.
|
|
130
|
+
*
|
|
131
|
+
* `entityKey` is stood in for rather than taken from the draft: it is the one
|
|
132
|
+
* field `validateProjection` never looks at, and a half-typed key would
|
|
133
|
+
* otherwise have to be valid before any join or identity could be checked. The
|
|
134
|
+
* missing or malformed key is reported separately, by `draftErrors`.
|
|
135
|
+
*/
|
|
136
|
+
function probeProjection(draft) {
|
|
137
|
+
return {
|
|
138
|
+
version: 1,
|
|
139
|
+
kind: "projection",
|
|
140
|
+
entityKey: PROBE_KEY,
|
|
141
|
+
baseTable: draft.baseTable,
|
|
142
|
+
joins: draft.joins,
|
|
143
|
+
fieldBindings: draft.fieldBindings,
|
|
144
|
+
identity: draft.identity
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* `validateProjection` appends its own `projection.` segment to the base path it
|
|
149
|
+
* is given, because in a config the base is the entity that owns the projection
|
|
150
|
+
* (`entities.accounts` → `entities.accounts.projection.identity`). A draft has
|
|
151
|
+
* no owning entity, so passing "projection" as the base produced
|
|
152
|
+
* `projection.projection.identity` — a path no caller could match against, and
|
|
153
|
+
* the reason `draftErrors` was only ever consumed as a flat list of messages.
|
|
154
|
+
*
|
|
155
|
+
* Passing an empty base and dropping the leading separator leaves one
|
|
156
|
+
* convention: every path here starts `projection.` exactly once, whether it came
|
|
157
|
+
* from this module or from the validator.
|
|
158
|
+
*/
|
|
159
|
+
function semanticErrors(draft, schema) {
|
|
160
|
+
return require_entity_config.validateProjection(probeProjection(draft), schema, "").map((error) => ({
|
|
161
|
+
...error,
|
|
162
|
+
path: error.path.startsWith(".") ? error.path.slice(1) : error.path
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Every reason this draft cannot be saved, one per cause and each anchored to
|
|
167
|
+
* the section that owns it.
|
|
168
|
+
*
|
|
169
|
+
* One lumped "Name the object and map a field." was all this used to return for
|
|
170
|
+
* an incomplete draft, at the bare path "projection". Two consequences, both
|
|
171
|
+
* bad: a builder had nowhere to attach the message, and — because it returned
|
|
172
|
+
* before validating — a fanning join under a `base_pk` identity stayed silent
|
|
173
|
+
* until the draft was otherwise complete, which is well after the point where
|
|
174
|
+
* the user chose the join.
|
|
175
|
+
*/
|
|
54
176
|
function draftErrors(draft, schema) {
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
path: "projection",
|
|
58
|
-
message: "
|
|
59
|
-
}
|
|
60
|
-
|
|
177
|
+
const errors = [];
|
|
178
|
+
if (draft.entityKey === "") errors.push({
|
|
179
|
+
path: "projection.entityKey",
|
|
180
|
+
message: "Key is required."
|
|
181
|
+
});
|
|
182
|
+
else if (!require_projection.PROJECTION_KEY_PATTERN.test(draft.entityKey)) errors.push({
|
|
183
|
+
path: "projection.entityKey",
|
|
184
|
+
message: `Key ${require_projection.PROJECTION_KEY_MESSAGE}.`
|
|
185
|
+
});
|
|
186
|
+
if (Object.keys(draft.fieldBindings).length === 0) errors.push({
|
|
187
|
+
path: "projection.fieldBindings",
|
|
188
|
+
message: "Map at least one field."
|
|
189
|
+
});
|
|
190
|
+
if (draft.baseTable === "") {
|
|
191
|
+
errors.push({
|
|
192
|
+
path: "projection.baseTable",
|
|
193
|
+
message: "Pick a base table."
|
|
194
|
+
});
|
|
195
|
+
return errors;
|
|
196
|
+
}
|
|
197
|
+
errors.push(...semanticErrors(draft, schema));
|
|
198
|
+
return errors;
|
|
61
199
|
}
|
|
62
|
-
/**
|
|
200
|
+
/**
|
|
201
|
+
* Per-join fan-out, for the builder's inline cardinality hints.
|
|
202
|
+
*
|
|
203
|
+
* Answered from the joins alone. Joins are chosen before the key and the fields
|
|
204
|
+
* are filled in, and a fan-out warning is precisely what should steer the
|
|
205
|
+
* identity choice while it is being made — requiring a complete draft put the
|
|
206
|
+
* hint after the decision it exists to inform.
|
|
207
|
+
*/
|
|
63
208
|
function describeCardinality(draft, schema) {
|
|
64
|
-
const projection =
|
|
65
|
-
if (!projection) return [];
|
|
209
|
+
const projection = probeProjection(draft);
|
|
66
210
|
const tablesByName = new Map(schema.map((t) => [t.name, t]));
|
|
67
211
|
const fanning = new Set(require_entity_config.fanOutJoins(projection, tablesByName).map((j) => j.key));
|
|
68
212
|
return projection.joins.map((join) => ({
|
|
@@ -77,6 +221,8 @@ exports.CRM_CONCEPTS = require_entity_config.CRM_CONCEPTS;
|
|
|
77
221
|
exports.ClivlyConfigError = require_entity_config.ClivlyConfigError;
|
|
78
222
|
exports.DEFAULT_VIEW_PREFIX = require_view_compiler.DEFAULT_VIEW_PREFIX;
|
|
79
223
|
exports.MATERIALIZING_CONCEPTS = require_entity_config.MATERIALIZING_CONCEPTS;
|
|
224
|
+
exports.PREVIEW_MAX_ROWS = require_view_compiler.PREVIEW_MAX_ROWS;
|
|
225
|
+
exports.addDraftJoin = addDraftJoin;
|
|
80
226
|
exports.authCustom = require_auth_adapter.authCustom;
|
|
81
227
|
exports.buildFilterFromDiscriminator = require_mapping_form.buildFilterFromDiscriminator;
|
|
82
228
|
exports.buildRelationships = require_mapping_form.buildRelationships;
|
|
@@ -86,10 +232,13 @@ exports.compileEntityViews = require_view_compiler.compileEntityViews;
|
|
|
86
232
|
exports.compileProjectionFrom = require_view_compiler.compileProjectionFrom;
|
|
87
233
|
exports.compileProjectionIdentity = require_view_compiler.compileProjectionIdentity;
|
|
88
234
|
exports.compileProjectionJoins = require_view_compiler.compileProjectionJoins;
|
|
235
|
+
exports.compileProjectionPreview = require_view_compiler.compileProjectionPreview;
|
|
236
|
+
exports.compileProjectionSelect = require_view_compiler.compileProjectionSelect;
|
|
89
237
|
exports.coversUniqueKey = require_entity_config.coversUniqueKey;
|
|
90
238
|
exports.defineClivlyConfig = require_entity_config.defineClivlyConfig;
|
|
91
239
|
exports.describeCardinality = describeCardinality;
|
|
92
240
|
exports.draftErrors = draftErrors;
|
|
241
|
+
exports.draftRefOptions = draftRefOptions;
|
|
93
242
|
exports.draftToProjection = draftToProjection;
|
|
94
243
|
exports.emptyProjectionDraft = emptyProjectionDraft;
|
|
95
244
|
exports.entitiesConfigSchema = require_entity_config.entitiesConfigSchema;
|
|
@@ -107,6 +256,7 @@ exports.reconcile = require_sync_engine.reconcile;
|
|
|
107
256
|
exports.relationshipSchema = require_entity_config.relationshipSchema;
|
|
108
257
|
exports.relationshipViaSignature = require_mapping_form.relationshipViaSignature;
|
|
109
258
|
exports.relationshipsSchema = require_entity_config.relationshipsSchema;
|
|
259
|
+
exports.removeDraftJoin = removeDraftJoin;
|
|
110
260
|
exports.runSync = require_sync_engine.runSync;
|
|
111
261
|
exports.suggestJoinConditions = suggestJoinConditions;
|
|
112
262
|
exports.validateEntitiesConfig = require_entity_config.validateEntitiesConfig;
|
package/dist/index.d.cts
CHANGED
|
@@ -35,10 +35,62 @@ declare function emptyProjectionDraft(baseTable: string): ProjectionDraft;
|
|
|
35
35
|
* Returns [] when no FK relates them — the user then picks manually.
|
|
36
36
|
*/
|
|
37
37
|
declare function suggestJoinConditions(baseTable: string, joinTable: string, schema: DiscoveredSchemaTable[], joinKey: string): ProjectionJoin["on"];
|
|
38
|
+
/**
|
|
39
|
+
* Add a join on `joinTable`, keyed and pre-filled.
|
|
40
|
+
*
|
|
41
|
+
* The key is the table's own name where that is free, because it is what the
|
|
42
|
+
* user already calls the table; a collision falls back to `_2`, `_3` and so on.
|
|
43
|
+
* Uniqueness is not cosmetic — the key is the SQL alias and the name every ref
|
|
44
|
+
* addresses the table by, so two joins sharing one would make every ref
|
|
45
|
+
* ambiguous. `base` is reserved for the base table and can never be taken.
|
|
46
|
+
*/
|
|
47
|
+
declare function addDraftJoin(draft: ProjectionDraft, joinTable: string, schema: DiscoveredSchemaTable[]): ProjectionDraft;
|
|
48
|
+
/**
|
|
49
|
+
* Remove a join, and everything that addressed it.
|
|
50
|
+
*
|
|
51
|
+
* Refs to a removed join are refs to a table the projection no longer declares.
|
|
52
|
+
* Left in place they fail validation as "unknown ref table", which blames the
|
|
53
|
+
* field — or, worse, the identity — for the join's removal. Identity falls back
|
|
54
|
+
* to the base primary key, the same default a new draft starts from.
|
|
55
|
+
*/
|
|
56
|
+
declare function removeDraftJoin(draft: ProjectionDraft, joinKey: string): ProjectionDraft;
|
|
57
|
+
/** One selectable table in the builder's column pickers. */
|
|
58
|
+
interface DraftRefOption {
|
|
59
|
+
columns: string[];
|
|
60
|
+
label: string;
|
|
61
|
+
ref: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Every table a field or identity may currently read from, in ref order: the
|
|
65
|
+
* base table first, then each join.
|
|
66
|
+
*
|
|
67
|
+
* Returns [] when the base table is not in the discovered schema — there is
|
|
68
|
+
* nothing truthful to offer, and inventing columns would let someone build a
|
|
69
|
+
* projection that cannot compile.
|
|
70
|
+
*/
|
|
71
|
+
declare function draftRefOptions(draft: ProjectionDraft, schema: DiscoveredSchemaTable[]): DraftRefOption[];
|
|
38
72
|
/** A draft becomes a projection once it has a valid key and ≥1 binding. */
|
|
39
73
|
declare function draftToProjection(draft: ProjectionDraft): ProjectionMapping | null;
|
|
74
|
+
/**
|
|
75
|
+
* Every reason this draft cannot be saved, one per cause and each anchored to
|
|
76
|
+
* the section that owns it.
|
|
77
|
+
*
|
|
78
|
+
* One lumped "Name the object and map a field." was all this used to return for
|
|
79
|
+
* an incomplete draft, at the bare path "projection". Two consequences, both
|
|
80
|
+
* bad: a builder had nowhere to attach the message, and — because it returned
|
|
81
|
+
* before validating — a fanning join under a `base_pk` identity stayed silent
|
|
82
|
+
* until the draft was otherwise complete, which is well after the point where
|
|
83
|
+
* the user chose the join.
|
|
84
|
+
*/
|
|
40
85
|
declare function draftErrors(draft: ProjectionDraft, schema: DiscoveredSchemaTable[]): ConfigValidationError[];
|
|
41
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Per-join fan-out, for the builder's inline cardinality hints.
|
|
88
|
+
*
|
|
89
|
+
* Answered from the joins alone. Joins are chosen before the key and the fields
|
|
90
|
+
* are filled in, and a fan-out warning is precisely what should steer the
|
|
91
|
+
* identity choice while it is being made — requiring a complete draft put the
|
|
92
|
+
* hint after the decision it exists to inform.
|
|
93
|
+
*/
|
|
42
94
|
declare function describeCardinality(draft: ProjectionDraft, schema: DiscoveredSchemaTable[]): Array<{
|
|
43
95
|
fansOut: boolean;
|
|
44
96
|
joinKey: string;
|
|
@@ -68,9 +120,37 @@ declare function quoteIdent(name: string): string;
|
|
|
68
120
|
* a pushed row's `id` identical to the one the view would have produced.
|
|
69
121
|
*/
|
|
70
122
|
declare function compileProjectionIdentity(projection: ProjectionMapping, basePkColumn: string): string;
|
|
123
|
+
declare function compileProjectionSelect(projection: ProjectionMapping, basePkColumn: string): {
|
|
124
|
+
columns: string[];
|
|
125
|
+
selectItems: string[];
|
|
126
|
+
};
|
|
71
127
|
declare function compileProjectionJoins(projection: ProjectionMapping): string[];
|
|
72
128
|
/** `FROM <baseTable> AS "base"` — the alias every ref resolves against. */
|
|
73
129
|
declare function compileProjectionFrom(projection: ProjectionMapping): string;
|
|
130
|
+
/**
|
|
131
|
+
* Hard ceiling on preview rows, regardless of what a caller asks for.
|
|
132
|
+
*
|
|
133
|
+
* A preview runs against a production database from a button a user can click
|
|
134
|
+
* as often as they like, so the cap belongs beside the query rather than in
|
|
135
|
+
* whichever caller happens to be passing a limit today.
|
|
136
|
+
*/
|
|
137
|
+
declare const PREVIEW_MAX_ROWS = 50;
|
|
138
|
+
/**
|
|
139
|
+
* One SELECT reading a few rows through an UNSAVED projection, for the derived
|
|
140
|
+
* object builder's preview.
|
|
141
|
+
*
|
|
142
|
+
* Lives here, beside the view compiler, because both sides of the round trip
|
|
143
|
+
* need the identical statement: an embedded org runs it against its own
|
|
144
|
+
* database from `@clivly.com/api`, and a cloud org's host app runs it from
|
|
145
|
+
* `@clivly/sdk`. Two copies would be free to disagree about what the user is
|
|
146
|
+
* being shown, which is the whole value of a preview.
|
|
147
|
+
*
|
|
148
|
+
* Columns are aliased by FIELD NAME, not by ref token. That is the deliberate
|
|
149
|
+
* difference from the push source, which aliases by token because cloud ingest
|
|
150
|
+
* reads `row[token]`; a preview is read by a person, in a table whose headers
|
|
151
|
+
* are the field names they just typed.
|
|
152
|
+
*/
|
|
153
|
+
declare function compileProjectionPreview(projection: ProjectionMapping, limit: number): string;
|
|
74
154
|
//#endregion
|
|
75
155
|
//#region src/projection-validate.d.ts
|
|
76
156
|
/**
|
|
@@ -105,4 +185,4 @@ declare function coversUniqueKey(table: DiscoveredSchemaTable, columns: string[]
|
|
|
105
185
|
/** Is this table's payload rich enough to validate a projection against? */
|
|
106
186
|
declare function hasRichMeta(table: DiscoveredSchemaTable): boolean;
|
|
107
187
|
//#endregion
|
|
108
|
-
export { type Activity, type ActivityType, BASE_REF, CANONICAL_FIELDS, type CRMAdapter, CRM_CONCEPTS, type ClivlyAuthAdapter, ClivlyConfigError, type ClivlyEntitiesConfig, type ClivlyEntityConfig, type ClivlyUser, type Company, type CompileOptions, type CompiledView, type ConfigValidationError, type ConfigValidationResult, type ConnectionLiveness, type Contact, type ContactFilters, type ContactStatus, type CreateActivityInput, type CreateCompanyInput, type CreateContactInput, type CreateDealInput, type CreateTaskInput, type CrmConcept, type CrmMembership, type CrmRole, DEFAULT_VIEW_PREFIX, type Deal, type DealStage, type DiscoveredColumnMeta, type DiscoveredSchemaTable, type EntityMappingRow, type EntitySyncResult, type ExplainResult, type FilterExpr, type FilterValue, type IntegrationFacts, type IntegrationState, type IntegrationStatus, type IntegrationStep, MATERIALIZING_CONCEPTS, type MaterializingConcept, type MembershipResolver, type MirrorRecord, type NextAction, type Note, type PersistAction, type ProjectionDraft, type ProjectionFieldBinding, type ProjectionIdentity, type ProjectionJoin, type ProjectionMapping, type ProjectionRef, type RecordSource, type RelationshipCandidate, type RelationshipSpec, type RelationshipVia, type RelationshipsMap, type SourceRow, type StepId, type StepProblem, type StepState, type SyncAction, type SyncResult, type SyncStore, type Task, type TaskEntityType, type TaskFilters, type TaskPriority, type TaskStatus, type UpdateContactInput, type UpdateDealInput, type UpdateTaskInput, authCustom, buildFilterFromDiscriminator, buildRelationships, columnMetaFor, compileEntityView, compileEntityViews, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, coversUniqueKey, defineClivlyConfig, describeCardinality, draftErrors, draftToProjection, emptyProjectionDraft, entitiesConfigSchema, explainEntitiesConfig, fanOutJoins, filterSchema, hasRichMeta, identityRefs, joinFansOut, mappingsToEntitiesConfig, primaryKeyColumns, projectionSchema, quoteIdent, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, runSync, suggestJoinConditions, validateEntitiesConfig, validateProjection };
|
|
188
|
+
export { type Activity, type ActivityType, BASE_REF, CANONICAL_FIELDS, type CRMAdapter, CRM_CONCEPTS, type ClivlyAuthAdapter, ClivlyConfigError, type ClivlyEntitiesConfig, type ClivlyEntityConfig, type ClivlyUser, type Company, type CompileOptions, type CompiledView, type ConfigValidationError, type ConfigValidationResult, type ConnectionLiveness, type Contact, type ContactFilters, type ContactStatus, type CreateActivityInput, type CreateCompanyInput, type CreateContactInput, type CreateDealInput, type CreateTaskInput, type CrmConcept, type CrmMembership, type CrmRole, DEFAULT_VIEW_PREFIX, type Deal, type DealStage, type DiscoveredColumnMeta, type DiscoveredSchemaTable, type DraftRefOption, type EntityMappingRow, type EntitySyncResult, type ExplainResult, type FilterExpr, type FilterValue, type IntegrationFacts, type IntegrationState, type IntegrationStatus, type IntegrationStep, MATERIALIZING_CONCEPTS, type MaterializingConcept, type MembershipResolver, type MirrorRecord, type NextAction, type Note, PREVIEW_MAX_ROWS, type PersistAction, type ProjectionDraft, type ProjectionFieldBinding, type ProjectionIdentity, type ProjectionJoin, type ProjectionMapping, type ProjectionRef, type RecordSource, type RelationshipCandidate, type RelationshipSpec, type RelationshipVia, type RelationshipsMap, type SourceRow, type StepId, type StepProblem, type StepState, type SyncAction, type SyncResult, type SyncStore, type Task, type TaskEntityType, type TaskFilters, type TaskPriority, type TaskStatus, type UpdateContactInput, type UpdateDealInput, type UpdateTaskInput, addDraftJoin, authCustom, buildFilterFromDiscriminator, buildRelationships, columnMetaFor, compileEntityView, compileEntityViews, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, compileProjectionPreview, compileProjectionSelect, coversUniqueKey, defineClivlyConfig, describeCardinality, draftErrors, draftRefOptions, draftToProjection, emptyProjectionDraft, entitiesConfigSchema, explainEntitiesConfig, fanOutJoins, filterSchema, hasRichMeta, identityRefs, joinFansOut, mappingsToEntitiesConfig, primaryKeyColumns, projectionSchema, quoteIdent, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, removeDraftJoin, runSync, suggestJoinConditions, validateEntitiesConfig, validateProjection };
|
package/dist/index.d.mts
CHANGED
|
@@ -35,10 +35,62 @@ declare function emptyProjectionDraft(baseTable: string): ProjectionDraft;
|
|
|
35
35
|
* Returns [] when no FK relates them — the user then picks manually.
|
|
36
36
|
*/
|
|
37
37
|
declare function suggestJoinConditions(baseTable: string, joinTable: string, schema: DiscoveredSchemaTable[], joinKey: string): ProjectionJoin["on"];
|
|
38
|
+
/**
|
|
39
|
+
* Add a join on `joinTable`, keyed and pre-filled.
|
|
40
|
+
*
|
|
41
|
+
* The key is the table's own name where that is free, because it is what the
|
|
42
|
+
* user already calls the table; a collision falls back to `_2`, `_3` and so on.
|
|
43
|
+
* Uniqueness is not cosmetic — the key is the SQL alias and the name every ref
|
|
44
|
+
* addresses the table by, so two joins sharing one would make every ref
|
|
45
|
+
* ambiguous. `base` is reserved for the base table and can never be taken.
|
|
46
|
+
*/
|
|
47
|
+
declare function addDraftJoin(draft: ProjectionDraft, joinTable: string, schema: DiscoveredSchemaTable[]): ProjectionDraft;
|
|
48
|
+
/**
|
|
49
|
+
* Remove a join, and everything that addressed it.
|
|
50
|
+
*
|
|
51
|
+
* Refs to a removed join are refs to a table the projection no longer declares.
|
|
52
|
+
* Left in place they fail validation as "unknown ref table", which blames the
|
|
53
|
+
* field — or, worse, the identity — for the join's removal. Identity falls back
|
|
54
|
+
* to the base primary key, the same default a new draft starts from.
|
|
55
|
+
*/
|
|
56
|
+
declare function removeDraftJoin(draft: ProjectionDraft, joinKey: string): ProjectionDraft;
|
|
57
|
+
/** One selectable table in the builder's column pickers. */
|
|
58
|
+
interface DraftRefOption {
|
|
59
|
+
columns: string[];
|
|
60
|
+
label: string;
|
|
61
|
+
ref: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Every table a field or identity may currently read from, in ref order: the
|
|
65
|
+
* base table first, then each join.
|
|
66
|
+
*
|
|
67
|
+
* Returns [] when the base table is not in the discovered schema — there is
|
|
68
|
+
* nothing truthful to offer, and inventing columns would let someone build a
|
|
69
|
+
* projection that cannot compile.
|
|
70
|
+
*/
|
|
71
|
+
declare function draftRefOptions(draft: ProjectionDraft, schema: DiscoveredSchemaTable[]): DraftRefOption[];
|
|
38
72
|
/** A draft becomes a projection once it has a valid key and ≥1 binding. */
|
|
39
73
|
declare function draftToProjection(draft: ProjectionDraft): ProjectionMapping | null;
|
|
74
|
+
/**
|
|
75
|
+
* Every reason this draft cannot be saved, one per cause and each anchored to
|
|
76
|
+
* the section that owns it.
|
|
77
|
+
*
|
|
78
|
+
* One lumped "Name the object and map a field." was all this used to return for
|
|
79
|
+
* an incomplete draft, at the bare path "projection". Two consequences, both
|
|
80
|
+
* bad: a builder had nowhere to attach the message, and — because it returned
|
|
81
|
+
* before validating — a fanning join under a `base_pk` identity stayed silent
|
|
82
|
+
* until the draft was otherwise complete, which is well after the point where
|
|
83
|
+
* the user chose the join.
|
|
84
|
+
*/
|
|
40
85
|
declare function draftErrors(draft: ProjectionDraft, schema: DiscoveredSchemaTable[]): ConfigValidationError[];
|
|
41
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Per-join fan-out, for the builder's inline cardinality hints.
|
|
88
|
+
*
|
|
89
|
+
* Answered from the joins alone. Joins are chosen before the key and the fields
|
|
90
|
+
* are filled in, and a fan-out warning is precisely what should steer the
|
|
91
|
+
* identity choice while it is being made — requiring a complete draft put the
|
|
92
|
+
* hint after the decision it exists to inform.
|
|
93
|
+
*/
|
|
42
94
|
declare function describeCardinality(draft: ProjectionDraft, schema: DiscoveredSchemaTable[]): Array<{
|
|
43
95
|
fansOut: boolean;
|
|
44
96
|
joinKey: string;
|
|
@@ -68,9 +120,37 @@ declare function quoteIdent(name: string): string;
|
|
|
68
120
|
* a pushed row's `id` identical to the one the view would have produced.
|
|
69
121
|
*/
|
|
70
122
|
declare function compileProjectionIdentity(projection: ProjectionMapping, basePkColumn: string): string;
|
|
123
|
+
declare function compileProjectionSelect(projection: ProjectionMapping, basePkColumn: string): {
|
|
124
|
+
columns: string[];
|
|
125
|
+
selectItems: string[];
|
|
126
|
+
};
|
|
71
127
|
declare function compileProjectionJoins(projection: ProjectionMapping): string[];
|
|
72
128
|
/** `FROM <baseTable> AS "base"` — the alias every ref resolves against. */
|
|
73
129
|
declare function compileProjectionFrom(projection: ProjectionMapping): string;
|
|
130
|
+
/**
|
|
131
|
+
* Hard ceiling on preview rows, regardless of what a caller asks for.
|
|
132
|
+
*
|
|
133
|
+
* A preview runs against a production database from a button a user can click
|
|
134
|
+
* as often as they like, so the cap belongs beside the query rather than in
|
|
135
|
+
* whichever caller happens to be passing a limit today.
|
|
136
|
+
*/
|
|
137
|
+
declare const PREVIEW_MAX_ROWS = 50;
|
|
138
|
+
/**
|
|
139
|
+
* One SELECT reading a few rows through an UNSAVED projection, for the derived
|
|
140
|
+
* object builder's preview.
|
|
141
|
+
*
|
|
142
|
+
* Lives here, beside the view compiler, because both sides of the round trip
|
|
143
|
+
* need the identical statement: an embedded org runs it against its own
|
|
144
|
+
* database from `@clivly.com/api`, and a cloud org's host app runs it from
|
|
145
|
+
* `@clivly/sdk`. Two copies would be free to disagree about what the user is
|
|
146
|
+
* being shown, which is the whole value of a preview.
|
|
147
|
+
*
|
|
148
|
+
* Columns are aliased by FIELD NAME, not by ref token. That is the deliberate
|
|
149
|
+
* difference from the push source, which aliases by token because cloud ingest
|
|
150
|
+
* reads `row[token]`; a preview is read by a person, in a table whose headers
|
|
151
|
+
* are the field names they just typed.
|
|
152
|
+
*/
|
|
153
|
+
declare function compileProjectionPreview(projection: ProjectionMapping, limit: number): string;
|
|
74
154
|
//#endregion
|
|
75
155
|
//#region src/projection-validate.d.ts
|
|
76
156
|
/**
|
|
@@ -105,4 +185,4 @@ declare function coversUniqueKey(table: DiscoveredSchemaTable, columns: string[]
|
|
|
105
185
|
/** Is this table's payload rich enough to validate a projection against? */
|
|
106
186
|
declare function hasRichMeta(table: DiscoveredSchemaTable): boolean;
|
|
107
187
|
//#endregion
|
|
108
|
-
export { type Activity, type ActivityType, BASE_REF, CANONICAL_FIELDS, type CRMAdapter, CRM_CONCEPTS, type ClivlyAuthAdapter, ClivlyConfigError, type ClivlyEntitiesConfig, type ClivlyEntityConfig, type ClivlyUser, type Company, type CompileOptions, type CompiledView, type ConfigValidationError, type ConfigValidationResult, type ConnectionLiveness, type Contact, type ContactFilters, type ContactStatus, type CreateActivityInput, type CreateCompanyInput, type CreateContactInput, type CreateDealInput, type CreateTaskInput, type CrmConcept, type CrmMembership, type CrmRole, DEFAULT_VIEW_PREFIX, type Deal, type DealStage, type DiscoveredColumnMeta, type DiscoveredSchemaTable, type EntityMappingRow, type EntitySyncResult, type ExplainResult, type FilterExpr, type FilterValue, type IntegrationFacts, type IntegrationState, type IntegrationStatus, type IntegrationStep, MATERIALIZING_CONCEPTS, type MaterializingConcept, type MembershipResolver, type MirrorRecord, type NextAction, type Note, type PersistAction, type ProjectionDraft, type ProjectionFieldBinding, type ProjectionIdentity, type ProjectionJoin, type ProjectionMapping, type ProjectionRef, type RecordSource, type RelationshipCandidate, type RelationshipSpec, type RelationshipVia, type RelationshipsMap, type SourceRow, type StepId, type StepProblem, type StepState, type SyncAction, type SyncResult, type SyncStore, type Task, type TaskEntityType, type TaskFilters, type TaskPriority, type TaskStatus, type UpdateContactInput, type UpdateDealInput, type UpdateTaskInput, authCustom, buildFilterFromDiscriminator, buildRelationships, columnMetaFor, compileEntityView, compileEntityViews, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, coversUniqueKey, defineClivlyConfig, describeCardinality, draftErrors, draftToProjection, emptyProjectionDraft, entitiesConfigSchema, explainEntitiesConfig, fanOutJoins, filterSchema, hasRichMeta, identityRefs, joinFansOut, mappingsToEntitiesConfig, primaryKeyColumns, projectionSchema, quoteIdent, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, runSync, suggestJoinConditions, validateEntitiesConfig, validateProjection };
|
|
188
|
+
export { type Activity, type ActivityType, BASE_REF, CANONICAL_FIELDS, type CRMAdapter, CRM_CONCEPTS, type ClivlyAuthAdapter, ClivlyConfigError, type ClivlyEntitiesConfig, type ClivlyEntityConfig, type ClivlyUser, type Company, type CompileOptions, type CompiledView, type ConfigValidationError, type ConfigValidationResult, type ConnectionLiveness, type Contact, type ContactFilters, type ContactStatus, type CreateActivityInput, type CreateCompanyInput, type CreateContactInput, type CreateDealInput, type CreateTaskInput, type CrmConcept, type CrmMembership, type CrmRole, DEFAULT_VIEW_PREFIX, type Deal, type DealStage, type DiscoveredColumnMeta, type DiscoveredSchemaTable, type DraftRefOption, type EntityMappingRow, type EntitySyncResult, type ExplainResult, type FilterExpr, type FilterValue, type IntegrationFacts, type IntegrationState, type IntegrationStatus, type IntegrationStep, MATERIALIZING_CONCEPTS, type MaterializingConcept, type MembershipResolver, type MirrorRecord, type NextAction, type Note, PREVIEW_MAX_ROWS, type PersistAction, type ProjectionDraft, type ProjectionFieldBinding, type ProjectionIdentity, type ProjectionJoin, type ProjectionMapping, type ProjectionRef, type RecordSource, type RelationshipCandidate, type RelationshipSpec, type RelationshipVia, type RelationshipsMap, type SourceRow, type StepId, type StepProblem, type StepState, type SyncAction, type SyncResult, type SyncStore, type Task, type TaskEntityType, type TaskFilters, type TaskPriority, type TaskStatus, type UpdateContactInput, type UpdateDealInput, type UpdateTaskInput, addDraftJoin, authCustom, buildFilterFromDiscriminator, buildRelationships, columnMetaFor, compileEntityView, compileEntityViews, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, compileProjectionPreview, compileProjectionSelect, coversUniqueKey, defineClivlyConfig, describeCardinality, draftErrors, draftRefOptions, draftToProjection, emptyProjectionDraft, entitiesConfigSchema, explainEntitiesConfig, fanOutJoins, filterSchema, hasRichMeta, identityRefs, joinFansOut, mappingsToEntitiesConfig, primaryKeyColumns, projectionSchema, quoteIdent, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, removeDraftJoin, runSync, suggestJoinConditions, validateEntitiesConfig, validateProjection };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { authCustom } from "./auth-adapter.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { _ as columnMetaFor, a as MATERIALIZING_CONCEPTS, b as primaryKeyColumns, c as entitiesConfigSchema, d as relationshipSchema, f as relationshipsSchema, g as joinFansOut, h as fanOutJoins, i as ClivlyConfigError, l as filterSchema, m as validateProjection, n as CRM_CONCEPTS, p as validateEntitiesConfig, s as defineClivlyConfig, t as CANONICAL_FIELDS, v as coversUniqueKey, y as hasRichMeta } from "./entity-config-
|
|
2
|
+
import { a as projectionSchema, i as identityRefs, n as PROJECTION_KEY_MESSAGE, r as PROJECTION_KEY_PATTERN, t as BASE_REF } from "./projection-BXw_iiw5.mjs";
|
|
3
|
+
import { _ as columnMetaFor, a as MATERIALIZING_CONCEPTS, b as primaryKeyColumns, c as entitiesConfigSchema, d as relationshipSchema, f as relationshipsSchema, g as joinFansOut, h as fanOutJoins, i as ClivlyConfigError, l as filterSchema, m as validateProjection, n as CRM_CONCEPTS, p as validateEntitiesConfig, s as defineClivlyConfig, t as CANONICAL_FIELDS, v as coversUniqueKey, y as hasRichMeta } from "./entity-config-DwGXbUo_.mjs";
|
|
4
4
|
import { buildFilterFromDiscriminator, buildRelationships, relationshipViaSignature } from "./mapping-form.mjs";
|
|
5
5
|
import { mappingsToEntitiesConfig } from "./mappings-config.mjs";
|
|
6
|
-
import { c as
|
|
6
|
+
import { c as compileProjectionIdentity, d as compileProjectionSelect, f as quoteIdent, i as explainEntitiesConfig, l as compileProjectionJoins, n as compileEntityView, o as PREVIEW_MAX_ROWS, r as compileEntityViews, s as compileProjectionFrom, t as DEFAULT_VIEW_PREFIX, u as compileProjectionPreview } from "./view-compiler-u3GGRgnv.mjs";
|
|
7
7
|
import { reconcile, runSync } from "./sync-engine.mjs";
|
|
8
8
|
//#region src/projection-draft.ts
|
|
9
9
|
function emptyProjectionDraft(baseTable) {
|
|
@@ -36,6 +36,78 @@ function suggestJoinConditions(baseTable, joinTable, schema, joinKey) {
|
|
|
36
36
|
});
|
|
37
37
|
return conditions;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Add a join on `joinTable`, keyed and pre-filled.
|
|
41
|
+
*
|
|
42
|
+
* The key is the table's own name where that is free, because it is what the
|
|
43
|
+
* user already calls the table; a collision falls back to `_2`, `_3` and so on.
|
|
44
|
+
* Uniqueness is not cosmetic — the key is the SQL alias and the name every ref
|
|
45
|
+
* addresses the table by, so two joins sharing one would make every ref
|
|
46
|
+
* ambiguous. `base` is reserved for the base table and can never be taken.
|
|
47
|
+
*/
|
|
48
|
+
function addDraftJoin(draft, joinTable, schema) {
|
|
49
|
+
const taken = /* @__PURE__ */ new Set([BASE_REF, ...draft.joins.map((j) => j.key)]);
|
|
50
|
+
let key = joinTable;
|
|
51
|
+
let suffix = 2;
|
|
52
|
+
while (taken.has(key)) {
|
|
53
|
+
key = `${joinTable}_${suffix}`;
|
|
54
|
+
suffix += 1;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
...draft,
|
|
58
|
+
joins: [...draft.joins, {
|
|
59
|
+
key,
|
|
60
|
+
table: joinTable,
|
|
61
|
+
type: "left",
|
|
62
|
+
on: suggestJoinConditions(draft.baseTable, joinTable, schema, key)
|
|
63
|
+
}]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Remove a join, and everything that addressed it.
|
|
68
|
+
*
|
|
69
|
+
* Refs to a removed join are refs to a table the projection no longer declares.
|
|
70
|
+
* Left in place they fail validation as "unknown ref table", which blames the
|
|
71
|
+
* field — or, worse, the identity — for the join's removal. Identity falls back
|
|
72
|
+
* to the base primary key, the same default a new draft starts from.
|
|
73
|
+
*/
|
|
74
|
+
function removeDraftJoin(draft, joinKey) {
|
|
75
|
+
const fieldBindings = Object.fromEntries(Object.entries(draft.fieldBindings).filter(([, binding]) => binding.from.table !== joinKey));
|
|
76
|
+
const identityDependsOnJoin = identityRefs(draft.identity, "").some((ref) => ref.table === joinKey);
|
|
77
|
+
return {
|
|
78
|
+
...draft,
|
|
79
|
+
joins: draft.joins.filter((join) => join.key !== joinKey),
|
|
80
|
+
fieldBindings,
|
|
81
|
+
identity: identityDependsOnJoin ? { kind: "base_pk" } : draft.identity
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Every table a field or identity may currently read from, in ref order: the
|
|
86
|
+
* base table first, then each join.
|
|
87
|
+
*
|
|
88
|
+
* Returns [] when the base table is not in the discovered schema — there is
|
|
89
|
+
* nothing truthful to offer, and inventing columns would let someone build a
|
|
90
|
+
* projection that cannot compile.
|
|
91
|
+
*/
|
|
92
|
+
function draftRefOptions(draft, schema) {
|
|
93
|
+
const byName = new Map(schema.map((table) => [table.name, table]));
|
|
94
|
+
const base = byName.get(draft.baseTable);
|
|
95
|
+
if (!base) return [];
|
|
96
|
+
const options = [{
|
|
97
|
+
ref: BASE_REF,
|
|
98
|
+
label: `${base.name} (${BASE_REF})`,
|
|
99
|
+
columns: base.columns
|
|
100
|
+
}];
|
|
101
|
+
for (const join of draft.joins) {
|
|
102
|
+
const table = byName.get(join.table);
|
|
103
|
+
if (table) options.push({
|
|
104
|
+
ref: join.key,
|
|
105
|
+
label: `${table.name} (${join.key})`,
|
|
106
|
+
columns: table.columns
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return options;
|
|
110
|
+
}
|
|
39
111
|
/** A draft becomes a projection once it has a valid key and ≥1 binding. */
|
|
40
112
|
function draftToProjection(draft) {
|
|
41
113
|
if (!PROJECTION_KEY_PATTERN.test(draft.entityKey)) return null;
|
|
@@ -50,18 +122,90 @@ function draftToProjection(draft) {
|
|
|
50
122
|
identity: draft.identity
|
|
51
123
|
};
|
|
52
124
|
}
|
|
125
|
+
const PROBE_KEY = "draft";
|
|
126
|
+
/**
|
|
127
|
+
* A draft rendered as a projection so the semantic checks can run against it
|
|
128
|
+
* while it is being written, not only once it is finished.
|
|
129
|
+
*
|
|
130
|
+
* `entityKey` is stood in for rather than taken from the draft: it is the one
|
|
131
|
+
* field `validateProjection` never looks at, and a half-typed key would
|
|
132
|
+
* otherwise have to be valid before any join or identity could be checked. The
|
|
133
|
+
* missing or malformed key is reported separately, by `draftErrors`.
|
|
134
|
+
*/
|
|
135
|
+
function probeProjection(draft) {
|
|
136
|
+
return {
|
|
137
|
+
version: 1,
|
|
138
|
+
kind: "projection",
|
|
139
|
+
entityKey: PROBE_KEY,
|
|
140
|
+
baseTable: draft.baseTable,
|
|
141
|
+
joins: draft.joins,
|
|
142
|
+
fieldBindings: draft.fieldBindings,
|
|
143
|
+
identity: draft.identity
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* `validateProjection` appends its own `projection.` segment to the base path it
|
|
148
|
+
* is given, because in a config the base is the entity that owns the projection
|
|
149
|
+
* (`entities.accounts` → `entities.accounts.projection.identity`). A draft has
|
|
150
|
+
* no owning entity, so passing "projection" as the base produced
|
|
151
|
+
* `projection.projection.identity` — a path no caller could match against, and
|
|
152
|
+
* the reason `draftErrors` was only ever consumed as a flat list of messages.
|
|
153
|
+
*
|
|
154
|
+
* Passing an empty base and dropping the leading separator leaves one
|
|
155
|
+
* convention: every path here starts `projection.` exactly once, whether it came
|
|
156
|
+
* from this module or from the validator.
|
|
157
|
+
*/
|
|
158
|
+
function semanticErrors(draft, schema) {
|
|
159
|
+
return validateProjection(probeProjection(draft), schema, "").map((error) => ({
|
|
160
|
+
...error,
|
|
161
|
+
path: error.path.startsWith(".") ? error.path.slice(1) : error.path
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Every reason this draft cannot be saved, one per cause and each anchored to
|
|
166
|
+
* the section that owns it.
|
|
167
|
+
*
|
|
168
|
+
* One lumped "Name the object and map a field." was all this used to return for
|
|
169
|
+
* an incomplete draft, at the bare path "projection". Two consequences, both
|
|
170
|
+
* bad: a builder had nowhere to attach the message, and — because it returned
|
|
171
|
+
* before validating — a fanning join under a `base_pk` identity stayed silent
|
|
172
|
+
* until the draft was otherwise complete, which is well after the point where
|
|
173
|
+
* the user chose the join.
|
|
174
|
+
*/
|
|
53
175
|
function draftErrors(draft, schema) {
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
path: "projection",
|
|
57
|
-
message: "
|
|
58
|
-
}
|
|
59
|
-
|
|
176
|
+
const errors = [];
|
|
177
|
+
if (draft.entityKey === "") errors.push({
|
|
178
|
+
path: "projection.entityKey",
|
|
179
|
+
message: "Key is required."
|
|
180
|
+
});
|
|
181
|
+
else if (!PROJECTION_KEY_PATTERN.test(draft.entityKey)) errors.push({
|
|
182
|
+
path: "projection.entityKey",
|
|
183
|
+
message: `Key ${PROJECTION_KEY_MESSAGE}.`
|
|
184
|
+
});
|
|
185
|
+
if (Object.keys(draft.fieldBindings).length === 0) errors.push({
|
|
186
|
+
path: "projection.fieldBindings",
|
|
187
|
+
message: "Map at least one field."
|
|
188
|
+
});
|
|
189
|
+
if (draft.baseTable === "") {
|
|
190
|
+
errors.push({
|
|
191
|
+
path: "projection.baseTable",
|
|
192
|
+
message: "Pick a base table."
|
|
193
|
+
});
|
|
194
|
+
return errors;
|
|
195
|
+
}
|
|
196
|
+
errors.push(...semanticErrors(draft, schema));
|
|
197
|
+
return errors;
|
|
60
198
|
}
|
|
61
|
-
/**
|
|
199
|
+
/**
|
|
200
|
+
* Per-join fan-out, for the builder's inline cardinality hints.
|
|
201
|
+
*
|
|
202
|
+
* Answered from the joins alone. Joins are chosen before the key and the fields
|
|
203
|
+
* are filled in, and a fan-out warning is precisely what should steer the
|
|
204
|
+
* identity choice while it is being made — requiring a complete draft put the
|
|
205
|
+
* hint after the decision it exists to inform.
|
|
206
|
+
*/
|
|
62
207
|
function describeCardinality(draft, schema) {
|
|
63
|
-
const projection =
|
|
64
|
-
if (!projection) return [];
|
|
208
|
+
const projection = probeProjection(draft);
|
|
65
209
|
const tablesByName = new Map(schema.map((t) => [t.name, t]));
|
|
66
210
|
const fanning = new Set(fanOutJoins(projection, tablesByName).map((j) => j.key));
|
|
67
211
|
return projection.joins.map((join) => ({
|
|
@@ -70,4 +214,4 @@ function describeCardinality(draft, schema) {
|
|
|
70
214
|
}));
|
|
71
215
|
}
|
|
72
216
|
//#endregion
|
|
73
|
-
export { BASE_REF, CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, DEFAULT_VIEW_PREFIX, MATERIALIZING_CONCEPTS, authCustom, buildFilterFromDiscriminator, buildRelationships, columnMetaFor, compileEntityView, compileEntityViews, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, coversUniqueKey, defineClivlyConfig, describeCardinality, draftErrors, draftToProjection, emptyProjectionDraft, entitiesConfigSchema, explainEntitiesConfig, fanOutJoins, filterSchema, hasRichMeta, identityRefs, joinFansOut, mappingsToEntitiesConfig, primaryKeyColumns, projectionSchema, quoteIdent, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, runSync, suggestJoinConditions, validateEntitiesConfig, validateProjection };
|
|
217
|
+
export { BASE_REF, CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, DEFAULT_VIEW_PREFIX, MATERIALIZING_CONCEPTS, PREVIEW_MAX_ROWS, addDraftJoin, authCustom, buildFilterFromDiscriminator, buildRelationships, columnMetaFor, compileEntityView, compileEntityViews, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, compileProjectionPreview, compileProjectionSelect, coversUniqueKey, defineClivlyConfig, describeCardinality, draftErrors, draftRefOptions, draftToProjection, emptyProjectionDraft, entitiesConfigSchema, explainEntitiesConfig, fanOutJoins, filterSchema, hasRichMeta, identityRefs, joinFansOut, mappingsToEntitiesConfig, primaryKeyColumns, projectionSchema, quoteIdent, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, removeDraftJoin, runSync, suggestJoinConditions, validateEntitiesConfig, validateProjection };
|
package/dist/mappings-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-25v_udeY.cjs");
|
|
3
3
|
//#region src/mappings-config.ts
|
|
4
4
|
const RESERVED_FIELD = "id";
|
|
5
5
|
/**
|
package/dist/mappings-config.mjs
CHANGED
|
@@ -93,4 +93,4 @@ function identityRefs(identity, basePkColumn) {
|
|
|
93
93
|
return identity.parts;
|
|
94
94
|
}
|
|
95
95
|
//#endregion
|
|
96
|
-
export { projectionSchema as i,
|
|
96
|
+
export { projectionSchema as a, identityRefs as i, PROJECTION_KEY_MESSAGE as n, PROJECTION_KEY_PATTERN as r, BASE_REF as t };
|
|
@@ -99,6 +99,12 @@ Object.defineProperty(exports, "BASE_REF", {
|
|
|
99
99
|
return BASE_REF;
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
|
+
Object.defineProperty(exports, "PROJECTION_KEY_MESSAGE", {
|
|
103
|
+
enumerable: true,
|
|
104
|
+
get: function() {
|
|
105
|
+
return PROJECTION_KEY_MESSAGE;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
102
108
|
Object.defineProperty(exports, "PROJECTION_KEY_PATTERN", {
|
|
103
109
|
enumerable: true,
|
|
104
110
|
get: function() {
|
package/dist/sync-engine.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_view_compiler = require("./view-compiler-
|
|
2
|
+
const require_view_compiler = require("./view-compiler-D30HNRnA.cjs");
|
|
3
3
|
//#region src/sync-engine.ts
|
|
4
4
|
/**
|
|
5
5
|
* Diff a view's rows against the current mirror population for one entity.
|
package/dist/sync-engine.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_projection = require("./projection-
|
|
1
|
+
const require_projection = require("./projection-OJYCYmS_.cjs");
|
|
2
2
|
//#region src/projection-sql.ts
|
|
3
3
|
/**
|
|
4
4
|
* SQL generation for projection-backed entities.
|
|
@@ -61,6 +61,38 @@ function compileProjectionJoins(projection) {
|
|
|
61
61
|
function compileProjectionFrom(projection) {
|
|
62
62
|
return `FROM ${quoteIdent(projection.baseTable)} AS ${quoteIdent(require_projection.BASE_REF)}`;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Hard ceiling on preview rows, regardless of what a caller asks for.
|
|
66
|
+
*
|
|
67
|
+
* A preview runs against a production database from a button a user can click
|
|
68
|
+
* as often as they like, so the cap belongs beside the query rather than in
|
|
69
|
+
* whichever caller happens to be passing a limit today.
|
|
70
|
+
*/
|
|
71
|
+
const PREVIEW_MAX_ROWS = 50;
|
|
72
|
+
/**
|
|
73
|
+
* One SELECT reading a few rows through an UNSAVED projection, for the derived
|
|
74
|
+
* object builder's preview.
|
|
75
|
+
*
|
|
76
|
+
* Lives here, beside the view compiler, because both sides of the round trip
|
|
77
|
+
* need the identical statement: an embedded org runs it against its own
|
|
78
|
+
* database from `@clivly.com/api`, and a cloud org's host app runs it from
|
|
79
|
+
* `@clivly/sdk`. Two copies would be free to disagree about what the user is
|
|
80
|
+
* being shown, which is the whole value of a preview.
|
|
81
|
+
*
|
|
82
|
+
* Columns are aliased by FIELD NAME, not by ref token. That is the deliberate
|
|
83
|
+
* difference from the push source, which aliases by token because cloud ingest
|
|
84
|
+
* reads `row[token]`; a preview is read by a person, in a table whose headers
|
|
85
|
+
* are the field names they just typed.
|
|
86
|
+
*/
|
|
87
|
+
function compileProjectionPreview(projection, limit) {
|
|
88
|
+
const { selectItems } = compileProjectionSelect(projection, projection.identity.kind === "base_pk" ? projection.identity.column ?? "id" : "id");
|
|
89
|
+
return [
|
|
90
|
+
`SELECT ${selectItems.join(", ")}`,
|
|
91
|
+
compileProjectionFrom(projection),
|
|
92
|
+
...compileProjectionJoins(projection),
|
|
93
|
+
`LIMIT ${Math.max(1, Math.min(Math.floor(limit), 50))}`
|
|
94
|
+
].join(" ");
|
|
95
|
+
}
|
|
64
96
|
//#endregion
|
|
65
97
|
//#region src/view-compiler.ts
|
|
66
98
|
/**
|
|
@@ -282,6 +314,12 @@ Object.defineProperty(exports, "DEFAULT_VIEW_PREFIX", {
|
|
|
282
314
|
return DEFAULT_VIEW_PREFIX;
|
|
283
315
|
}
|
|
284
316
|
});
|
|
317
|
+
Object.defineProperty(exports, "PREVIEW_MAX_ROWS", {
|
|
318
|
+
enumerable: true,
|
|
319
|
+
get: function() {
|
|
320
|
+
return PREVIEW_MAX_ROWS;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
285
323
|
Object.defineProperty(exports, "compileEntityView", {
|
|
286
324
|
enumerable: true,
|
|
287
325
|
get: function() {
|
|
@@ -312,6 +350,18 @@ Object.defineProperty(exports, "compileProjectionJoins", {
|
|
|
312
350
|
return compileProjectionJoins;
|
|
313
351
|
}
|
|
314
352
|
});
|
|
353
|
+
Object.defineProperty(exports, "compileProjectionPreview", {
|
|
354
|
+
enumerable: true,
|
|
355
|
+
get: function() {
|
|
356
|
+
return compileProjectionPreview;
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
Object.defineProperty(exports, "compileProjectionSelect", {
|
|
360
|
+
enumerable: true,
|
|
361
|
+
get: function() {
|
|
362
|
+
return compileProjectionSelect;
|
|
363
|
+
}
|
|
364
|
+
});
|
|
315
365
|
Object.defineProperty(exports, "explainEntitiesConfig", {
|
|
316
366
|
enumerable: true,
|
|
317
367
|
get: function() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as identityRefs, t as BASE_REF } from "./projection-BXw_iiw5.mjs";
|
|
2
2
|
//#region src/projection-sql.ts
|
|
3
3
|
/**
|
|
4
4
|
* SQL generation for projection-backed entities.
|
|
@@ -61,6 +61,38 @@ function compileProjectionJoins(projection) {
|
|
|
61
61
|
function compileProjectionFrom(projection) {
|
|
62
62
|
return `FROM ${quoteIdent(projection.baseTable)} AS ${quoteIdent(BASE_REF)}`;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Hard ceiling on preview rows, regardless of what a caller asks for.
|
|
66
|
+
*
|
|
67
|
+
* A preview runs against a production database from a button a user can click
|
|
68
|
+
* as often as they like, so the cap belongs beside the query rather than in
|
|
69
|
+
* whichever caller happens to be passing a limit today.
|
|
70
|
+
*/
|
|
71
|
+
const PREVIEW_MAX_ROWS = 50;
|
|
72
|
+
/**
|
|
73
|
+
* One SELECT reading a few rows through an UNSAVED projection, for the derived
|
|
74
|
+
* object builder's preview.
|
|
75
|
+
*
|
|
76
|
+
* Lives here, beside the view compiler, because both sides of the round trip
|
|
77
|
+
* need the identical statement: an embedded org runs it against its own
|
|
78
|
+
* database from `@clivly.com/api`, and a cloud org's host app runs it from
|
|
79
|
+
* `@clivly/sdk`. Two copies would be free to disagree about what the user is
|
|
80
|
+
* being shown, which is the whole value of a preview.
|
|
81
|
+
*
|
|
82
|
+
* Columns are aliased by FIELD NAME, not by ref token. That is the deliberate
|
|
83
|
+
* difference from the push source, which aliases by token because cloud ingest
|
|
84
|
+
* reads `row[token]`; a preview is read by a person, in a table whose headers
|
|
85
|
+
* are the field names they just typed.
|
|
86
|
+
*/
|
|
87
|
+
function compileProjectionPreview(projection, limit) {
|
|
88
|
+
const { selectItems } = compileProjectionSelect(projection, projection.identity.kind === "base_pk" ? projection.identity.column ?? "id" : "id");
|
|
89
|
+
return [
|
|
90
|
+
`SELECT ${selectItems.join(", ")}`,
|
|
91
|
+
compileProjectionFrom(projection),
|
|
92
|
+
...compileProjectionJoins(projection),
|
|
93
|
+
`LIMIT ${Math.max(1, Math.min(Math.floor(limit), 50))}`
|
|
94
|
+
].join(" ");
|
|
95
|
+
}
|
|
64
96
|
//#endregion
|
|
65
97
|
//#region src/view-compiler.ts
|
|
66
98
|
/**
|
|
@@ -276,4 +308,4 @@ async function explainEntitiesConfig(config, run, options = {}) {
|
|
|
276
308
|
return results;
|
|
277
309
|
}
|
|
278
310
|
//#endregion
|
|
279
|
-
export { explainViewExecutionError as a,
|
|
311
|
+
export { explainViewExecutionError as a, compileProjectionIdentity as c, compileProjectionSelect as d, quoteIdent as f, explainEntitiesConfig as i, compileProjectionJoins as l, compileEntityView as n, PREVIEW_MAX_ROWS as o, compileEntityViews as r, compileProjectionFrom as s, DEFAULT_VIEW_PREFIX as t, compileProjectionPreview as u };
|
package/dist/view-compiler.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_view_compiler = require("./view-compiler-
|
|
2
|
+
const require_view_compiler = require("./view-compiler-D30HNRnA.cjs");
|
|
3
3
|
exports.DEFAULT_VIEW_PREFIX = require_view_compiler.DEFAULT_VIEW_PREFIX;
|
|
4
4
|
exports.compileEntityView = require_view_compiler.compileEntityView;
|
|
5
5
|
exports.compileEntityViews = require_view_compiler.compileEntityViews;
|
package/dist/view-compiler.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as explainViewExecutionError, i as explainEntitiesConfig, n as compileEntityView, r as compileEntityViews, t as DEFAULT_VIEW_PREFIX } from "./view-compiler-
|
|
1
|
+
import { a as explainViewExecutionError, i as explainEntitiesConfig, n as compileEntityView, r as compileEntityViews, t as DEFAULT_VIEW_PREFIX } from "./view-compiler-u3GGRgnv.mjs";
|
|
2
2
|
export { DEFAULT_VIEW_PREFIX, compileEntityView, compileEntityViews, explainEntitiesConfig, explainViewExecutionError };
|