@clivly/core 0.9.5 → 0.9.6

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 CHANGED
@@ -188,10 +188,18 @@ every rule is a pure function of draft + schema:
188
188
  | Helper | Purpose |
189
189
  | --- | --- |
190
190
  | `emptyProjectionDraft(baseTable)` | A blank draft |
191
+ | `addDraftJoin(draft, joinTable, schema)` | Add a join, keyed uniquely and pre-filled from foreign-key metadata |
192
+ | `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 |
193
+ | `draftRefOptions(draft, schema)` | The tables and columns a field or identity may currently read from, in ref order |
191
194
  | `suggestJoinConditions(base, joinTable, schema, key)` | Pre-fill a join's `ON` from foreign-key metadata |
192
195
  | `draftToProjection(draft)` | `ProjectionMapping`, or `null` while incomplete |
193
- | `draftErrors(draft, schema)` | Validation errors for the current draft |
194
- | `describeCardinality(draft, schema)` | Per-join `{ joinKey, fansOut }` for inline hints |
196
+ | `draftErrors(draft, schema)` | Validation errors, one per cause, each anchored to the section that owns it (`projection.entityKey`, `projection.joins`, …) |
197
+ | `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 |
198
+
199
+ `compileProjectionPreview(projection, limit)` compiles the same object to a
200
+ capped, read-only `SELECT` — used by both sides of the dashboard's preview, so
201
+ an embedded org and a cloud org's host app cannot disagree about what the user
202
+ is being shown.
195
203
 
196
204
  Full guide, including how to choose an identity and the current limits:
197
205
  [`docs/guides/derived-objects.md`](https://github.com/amani-joseph/clivly.com/blob/master/docs/guides/derived-objects.md).
@@ -1,4 +1,4 @@
1
- const require_projection = require("./projection-DFgCoyZl.cjs");
1
+ const require_projection = require("./projection-OJYCYmS_.cjs");
2
2
  let zod = require("zod");
3
3
  //#region src/schema-meta.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { i as projectionSchema, r as identityRefs, t as BASE_REF } from "./projection-SCNrLJMV.mjs";
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
  /**
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_entity_config = require("./entity-config-ZSv3XuW_.cjs");
2
+ const require_entity_config = require("./entity-config-BAaZOIID.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;
@@ -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-Dt_KfgRl.mjs";
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-kusaRiDs.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-ZSv3XuW_.cjs");
2
+ const require_entity_config = require("./entity-config-BAaZOIID.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-Dt_KfgRl.mjs";
1
+ import { a as MATERIALIZING_CONCEPTS } from "./entity-config-kusaRiDs.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-DFgCoyZl.cjs");
4
- const require_entity_config = require("./entity-config-ZSv3XuW_.cjs");
3
+ const require_projection = require("./projection-OJYCYmS_.cjs");
4
+ const require_entity_config = require("./entity-config-BAaZOIID.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-DTGSwTcz.cjs");
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 projection = draftToProjection(draft);
56
- if (!projection) return [{
57
- path: "projection",
58
- message: "Name the object and map a field."
59
- }];
60
- return require_entity_config.validateProjection(projection, schema, "projection");
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
- /** Per-join fan-out, for the builder's inline cardinality hints. */
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 = draftToProjection(draft);
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
- /** Per-join fan-out, for the builder's inline cardinality hints. */
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
- /** Per-join fan-out, for the builder's inline cardinality hints. */
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 { i as projectionSchema, n as PROJECTION_KEY_PATTERN, r as identityRefs, t as BASE_REF } from "./projection-SCNrLJMV.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-Dt_KfgRl.mjs";
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-kusaRiDs.mjs";
4
4
  import { buildFilterFromDiscriminator, buildRelationships, relationshipViaSignature } from "./mapping-form.mjs";
5
5
  import { mappingsToEntitiesConfig } from "./mappings-config.mjs";
6
- import { c as compileProjectionJoins, i as explainEntitiesConfig, l as quoteIdent, n as compileEntityView, o as compileProjectionFrom, r as compileEntityViews, s as compileProjectionIdentity, t as DEFAULT_VIEW_PREFIX } from "./view-compiler-DjzRXiAJ.mjs";
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 projection = draftToProjection(draft);
55
- if (!projection) return [{
56
- path: "projection",
57
- message: "Name the object and map a field."
58
- }];
59
- return validateProjection(projection, schema, "projection");
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
- /** Per-join fan-out, for the builder's inline cardinality hints. */
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 = draftToProjection(draft);
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 };
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_entity_config = require("./entity-config-ZSv3XuW_.cjs");
2
+ const require_entity_config = require("./entity-config-BAaZOIID.cjs");
3
3
  //#region src/mappings-config.ts
4
4
  const RESERVED_FIELD = "id";
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { t as CANONICAL_FIELDS } from "./entity-config-Dt_KfgRl.mjs";
1
+ import { t as CANONICAL_FIELDS } from "./entity-config-kusaRiDs.mjs";
2
2
  //#region src/mappings-config.ts
3
3
  const RESERVED_FIELD = "id";
4
4
  /**
@@ -93,4 +93,4 @@ function identityRefs(identity, basePkColumn) {
93
93
  return identity.parts;
94
94
  }
95
95
  //#endregion
96
- export { projectionSchema as i, PROJECTION_KEY_PATTERN as n, identityRefs as r, BASE_REF as t };
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() {
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_view_compiler = require("./view-compiler-DTGSwTcz.cjs");
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.
@@ -1,4 +1,4 @@
1
- import { r as compileEntityViews } from "./view-compiler-DjzRXiAJ.mjs";
1
+ import { r as compileEntityViews } from "./view-compiler-u3GGRgnv.mjs";
2
2
  //#region src/sync-engine.ts
3
3
  /**
4
4
  * Diff a view's rows against the current mirror population for one entity.
@@ -1,4 +1,4 @@
1
- const require_projection = require("./projection-DFgCoyZl.cjs");
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 { r as identityRefs, t as BASE_REF } from "./projection-SCNrLJMV.mjs";
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, compileProjectionJoins as c, explainEntitiesConfig as i, quoteIdent as l, compileEntityView as n, compileProjectionFrom as o, compileEntityViews as r, compileProjectionIdentity as s, DEFAULT_VIEW_PREFIX as t };
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 };
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_view_compiler = require("./view-compiler-DTGSwTcz.cjs");
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;
@@ -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-DjzRXiAJ.mjs";
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clivly/core",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "ORM/auth-agnostic CRM adapter contracts and shared domain types for Clivly.",