import { Activity, ActivityType, Company, CompanyFilters, Contact, ContactFilters, ContactStatus, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, CrmRole, Deal, DealStage, Note, OPEN_STAGE_TYPE, Page, PageRequest, PipelineConfig, PipelineKind, PipelineStageConfig, RecordSource, StageType, Task, TaskEntityType, TaskFilters, TaskPriority, TaskStatus, UpdateCompanyInput, UpdateContactInput, UpdateDealInput, UpdateTaskInput, ValueInterval, isClosedStageType } from "./types.cjs"; import { CRMAdapter } from "./adapter.cjs"; import { ClivlyAuthAdapter, ClivlyUser, CrmMembership, MembershipResolver, authCustom } from "./auth-adapter.cjs"; import { A as relationshipsSchema, B as identityRefs, D as missingRequiredFields, E as filterSchema, F as ProjectionFieldBinding, I as ProjectionIdentity, L as ProjectionJoin, M as validateEntitiesConfig, N as BASE_REF, O as projectionFields, P as PROJECTION_KEY_PATTERN, R as ProjectionMapping, S as RelationshipsMap, T as entitiesConfigSchema, V as projectionSchema, _ as MATERIALIZING_CONCEPTS, a as ClivlyEntitiesConfig, b as RelationshipSpec, d as CrmConcept, g as FilterValue, h as FilterExpr, i as ClivlyConfigError, j as requiredFieldsFor, k as relationshipSchema, l as ConfigValidationError, m as DiscoveredSchemaTable, n as CRM_CONCEPTS, p as DiscoveredColumnMeta, s as ClivlyEntityConfig, t as CANONICAL_FIELDS, u as ConfigValidationResult, v as MaterializingConcept, w as defineClivlyConfig, x as RelationshipVia, y as REQUIRED_FIELDS, z as ProjectionRef } from "./entity-config-OhC4Otkz.cjs"; import { ConnectionLiveness, IntegrationFacts, IntegrationState, IntegrationStatus, IntegrationStep, NextAction, StepId, StepProblem, StepState } from "./integration-state.cjs"; import { RelationshipCandidate, buildFilterFromDiscriminator, buildRelationships, relationshipViaSignature } from "./mapping-form.cjs"; import { EntityMappingRow, mappingsToEntitiesConfig } from "./mappings-config.cjs"; import { CompileOptions, CompiledView, DEFAULT_VIEW_PREFIX, ExplainResult, compileEntityView, compileEntityViews, explainEntitiesConfig } from "./view-compiler.cjs"; import { EntitySyncResult, MirrorRecord, PersistAction, SourceRow, SyncAction, SyncResult, SyncStore, reconcile, runSync } from "./sync-engine.cjs"; //#region src/deal-value.d.ts /** * Value derived as a flat fee plus a per-seat price that depends on the plan. * * Every field name is configuration and every plan key is data, so a second * subscription business configures this without a code change. The formula is * fixed with named slots rather than an expression language — the same choice * D8 made for stage-rule conditions. */ interface PerSeatValueSource { /** Flat monthly fee in cents, added before the interval is normalised. */ baseFeeCents?: number; /** Mapped column holding "month" or "year". Absent means always monthly. */ intervalField?: string; kind: "per_seat"; /** Mapped column holding the plan key. */ planField: string; /** Unit price in cents, keyed by the value found in `planField`. */ plans: Record; /** Mapped column holding the seat count. */ seatField: string; /** Stage types whose deals are worth nothing (usually `["lost"]`). */ zeroInStageTypes?: string[]; } /** * The value one deal should have, in cents, or null to leave it alone. * * Returning null rather than 0 for unusable input is deliberate (D33). A * renamed column would otherwise zero an entire pipeline silently, and a zeroed * board reads as a business problem rather than a configuration problem. The * unmapped case is surfaced through the rule-health warning surface instead of * being written into the data — the same fail-safe `matchesCondition` applies. */ export declare function computeDealValue(source: PerSeatValueSource, fields: Record, mappedFields: Set, stageType: string): number | null; //#endregion //#region src/host-owned-fields.d.ts /** * Which fields a CRM record lets a user edit. * * A record synced from the host app carries a `sourceRef`. Its identity fields * are rewritten from the host view on every sync run, so an edit to one would * be silently reverted minutes later — the user sees their change accepted, * then gone, with nothing explaining why. That is worse than refusing it. * * So host-owned fields are rejected at the API, not merely disabled in the UI: * the UI is not the security boundary, and the SDK and any future public API * reach the same routers. * * Records created inside the CRM have no `sourceRef` and are fully editable — * nothing else owns them. * * This is the cheap half of a capability matrix. It answers "may a user write * this?" but not "may the sync engine write this?", which stays implicit * because sync bypasses these routers entirely. An override model (the * `stageOverrideAt` pattern on `crm_deals`) can replace this later without * changing callers. */ /** Contact fields the host view owns when the row is synced. */ declare const HOST_OWNED_CONTACT_FIELDS: readonly ["email", "name"]; /** * Company fields the host view owns when the row is synced. * * `email` and `phone` joined this list in the same change that made them * mappable (migration 0048). They are not identity fields, but ownership here * follows what sync rewrites, not what feels identity-like: both flow from the * host view on every run, exactly as `email` does for contacts above. Widening * `CANONICAL_FIELDS.company` without widening this list is the specific bug * this module's doc comment calls worse than refusing the edit. * * `primaryContactId` is deliberately absent: it is a CRM-side judgement about * who to talk to, never sourced from the host, so it stays editable on synced * rows. */ declare const HOST_OWNED_COMPANY_FIELDS: readonly ["domain", "name", "email", "phone"]; type HostOwnedContactField = (typeof HOST_OWNED_CONTACT_FIELDS)[number]; type HostOwnedCompanyField = (typeof HOST_OWNED_COMPANY_FIELDS)[number]; /** The subset of a record this module needs to make its decision. */ interface SourcedRecord { sourceRef?: string | null; } /** * True when the record is owned by a host view rather than by the CRM. * * Deliberately keyed on `sourceRef` and not `entityRole`: `entityRole` is a * mapping label that can be absent on a synced row, while `sourceRef` is the * identity the sync engine reconciles against and is set for exactly the rows * sync will rewrite. */ export declare function isHostOwned(record: SourcedRecord): boolean; /** * Host-owned contact fields this patch would change, or `[]` when the patch is * allowed. Callers turn a non-empty result into a 400 naming the fields. */ export declare function rejectedContactFields(contact: SourcedRecord, patch: Readonly>): HostOwnedContactField[]; /** Host-owned company fields this patch would change, or `[]`. */ export declare function rejectedCompanyFields(company: SourcedRecord, patch: Readonly>): HostOwnedCompanyField[]; /** * The message shown when a write is refused. Names the fields and says where * they come from, so the reason is actionable rather than a bare "forbidden". */ export declare function hostOwnedFieldMessage(fields: readonly string[]): string; //#endregion //#region src/projection-cardinality.d.ts export declare function joinFansOut(join: ProjectionJoin, joinTable: DiscoveredSchemaTable): boolean; /** Every join in the projection that can multiply rows. */ export declare function fanOutJoins(projection: ProjectionMapping, tablesByName: Map): ProjectionJoin[]; //#endregion //#region src/projection-sql.d.ts /** * SQL generation for projection-backed entities. * * Base and joins are aliased by their ref key (`base`, then each join's `key`), * which is what allows the same table to be joined twice and keeps generated * SQL independent of host table names. * * Pure string generation — no DB access. Identifiers are escaped so output is * deterministic and injection-safe. */ /** Quote a single identifier part, escaping embedded double-quotes. */ export declare function quoteIdent(name: string): string; /** * The identity expression. Composite identities concatenate their parts with * `::`; validation guarantees every part is NOT NULL and non-text, which is * what makes the concatenation collision-free. * * Exported unaliased because two paths need the same expression under different * names: the view compiler aliases it `id`, and the SDK's projection push source * also orders and keyset-paginates by it. Sharing one definition is what keeps * a pushed row's `id` identical to the one the view would have produced. */ export declare function compileProjectionIdentity(projection: ProjectionMapping, basePkColumn: string): string; export declare function compileProjectionSelect(projection: ProjectionMapping, basePkColumn: string): { columns: string[]; selectItems: string[]; }; export declare function compileProjectionJoins(projection: ProjectionMapping): string[]; /** `FROM AS "base"` — the alias every ref resolves against. */ export declare function compileProjectionFrom(projection: ProjectionMapping): string; /** * Hard ceiling on preview rows, regardless of what a caller asks for. * * A preview runs against a production database from a button a user can click * as often as they like, so the cap belongs beside the query rather than in * whichever caller happens to be passing a limit today. */ export declare const PREVIEW_MAX_ROWS = 50; /** * One SELECT reading a few rows through an UNSAVED projection, for the derived * object builder's preview. * * Lives here, beside the view compiler, because both sides of the round trip * need the identical statement: an embedded org runs it against its own * database from `@clivly.com/api`, and a cloud org's host app runs it from * `@clivly/sdk`. Two copies would be free to disagree about what the user is * being shown, which is the whole value of a preview. * * Columns are aliased by FIELD NAME, not by ref token. That is the deliberate * difference from the push source, which aliases by token because cloud ingest * reads `row[token]`; a preview is read by a person, in a table whose headers * are the field names they just typed. */ export declare function compileProjectionPreview(projection: ProjectionMapping, limit: number): string; //#endregion //#region src/projection-validate.d.ts /** * A derived object's filter is evaluated in the cloud against pushed rows, so * it can only name columns of the base table. * * Every rejected shape here fails *silently* today, which is the whole reason * the check exists — a filter that cannot be honoured must error, never quietly * return the wrong rows. The three shapes fail in different directions: * * - `$raw` is opaque SQL meant for the embedded model. The cloud path cannot * evaluate it and passes every row, so the filter is ignored and the object * syncs UNFILTERED. * - A joined-table column resolves to nothing on a pushed row, so under a * positive operator (`eq`, `in`) no row matches and the object syncs EMPTY. * - The same unresolvable column under a NEGATIVE operator (`ne`, `isNull`, * `notIn`) is the dangerous one: the missing cell reads as null, so the * predicate is true for every row and a filter meant to narrow the sync * ADMITS EVERYTHING. Nobody notices extra rows arriving. * * Rejecting an unresolvable column closes the last two together. */ export declare function validateProjectionFilter(filter: FilterExpr | undefined, projection: ProjectionMapping, schema: DiscoveredSchemaTable[], base: string): ConfigValidationError[]; /** * Validate a projection against the discovered schema. Returns every error * found (not just the first) so a builder UI can show them all at once. */ export declare function validateProjection(projection: ProjectionMapping, schema: DiscoveredSchemaTable[], base: string): ConfigValidationError[]; //#endregion //#region src/schema-meta.d.ts /** * Accessors over a discovered table's schema-v2 metadata. * * Every helper treats *absent* metadata as **unknown**, never as a negative * fact. `coversUniqueKey` returning false on a v1 payload is what makes * projection validation fail closed: we cannot prove a join is many-to-one, so * we refuse to compile it rather than guess. */ export declare function columnMetaFor(table: DiscoveredSchemaTable, column: string): DiscoveredColumnMeta | undefined; /** * Every primary-key column. More than one entry means a composite PK, which is * why `base_pk` identity is rejected for such tables. */ export declare function primaryKeyColumns(table: DiscoveredSchemaTable): string[]; /** * Do `columns` contain every column of at least one unique key on `table`? * * This is the fan-out test. A join whose ON columns cover a unique key of the * joined table matches at most one row per base row; one that does not can * match many. */ export declare function coversUniqueKey(table: DiscoveredSchemaTable, columns: string[]): boolean; /** * A discovered table whose enrichment metadata is known to be present. * * This narrows only the *presence* of the two optional fields. It says nothing * about their contents — an empty `uniqueConstraints` is a known-empty fact * (the table genuinely has no unique keys), which is exactly the distinction * `hasRichMeta` has always drawn and must keep drawing. */ type RichMetaTable = DiscoveredSchemaTable & { columnsMeta: DiscoveredColumnMeta[]; uniqueConstraints: string[][]; }; /** * Is this table's payload rich enough to validate a projection against? * * A type predicate rather than a plain boolean so the narrowing is visible to * the compiler: callers that guard on this can iterate `columnsMeta` directly * instead of casting, and a cast that outlives its guard becomes a type error * rather than a silent walk over `undefined`. * * The invariant is unchanged: this reports whether the metadata is *known*, * never whether the table has keys. Absent metadata stays UNKNOWN. */ export declare function hasRichMeta(table: DiscoveredSchemaTable): table is RichMetaTable; //#endregion //#region src/slug.d.ts /** * The one `slugify`. * * Three byte-identical copies existed before this module — in `@clivly/api` * (the custom object-type slug), in `apps/web`'s onboarding route (the * organisation slug), and in `@clivly/sdk`. The first two now import this; the * SDK's copy stays where it is on purpose, because `@clivly/sdk` cannot depend * on this package and its own comment records that its pattern is deliberately * kept in step with the server's by hand. * * Note that the two callers this replaces slug *different things* — an * object type and an organisation. They share a transform, not a namespace. * Sharing the function stops the transform drifting; it does not make those two * identifiers the same identifier. */ /** * Lowercase, trim, collapse every run of non-alphanumerics to one hyphen, and * drop leading/trailing hyphens. `"Participant Info"` becomes * `"participant-info"`. * * The result is NOT guaranteed to be an acceptable slug. Both * `OBJECT_TYPE_SLUG_PATTERN` (server) and `PROJECTION_KEY_PATTERN` (this * package) require a leading letter, and this function happily returns * `"2024-invoices"` for `"2024 Invoices"`. Callers that need an * acceptance-guaranteed identifier want `toObjectTypeSlug`. */ export declare function slugify(value: string): string; /** * A slug the server will accept, derived from a display name. * * `slugify` alone is not enough: the server's `OBJECT_TYPE_SLUG_PATTERN` * (`apps/server/src/index.ts`) demands a leading letter, so a name beginning * with a digit slugs to something the platform rejects at push time — long * after the object was authored. `@clivly/sdk`'s `custom-slug.ts` already knows * this case and deliberately withholds `"2024-invoices"` as a suggestion * because it would be rejected too. * * The prefix is applied ONLY when the derived slug does not already start with * a letter, so the overwhelmingly common name is untouched. * * This value must be shown to whoever authored the name. It is the identifier * the host app declares as its `objectType`, so a slug derived silently is a * slug the developer cannot copy into their config. * * Returns an empty string for a name with no alphanumerics at all — the caller * decides whether that is "keep typing" or an error, since only it knows * whether the field is still being edited. */ export declare function toObjectTypeSlug(displayName: string): string; //#endregion //#region src/stage-rules.d.ts type ConditionOp = "is_empty" | "is_not_empty" | "equals" | "not_equals" | "greater_than" | "less_than" | "before" | "after"; interface Condition { field: string; op: ConditionOp; value?: string; } interface EvaluableRule { conditions: Condition[]; id: string; } /** * Whether one condition holds for a host row. * * `mappedFields` is the set of columns the organization actually exposed. A * condition naming anything outside it never matches, whatever the operator: a * rule referencing a dropped column must fail safe, because treating the field * as null would make `is_empty` match every deal. */ export declare function matchesCondition(condition: Condition, fields: Record, mappedFields: Set): boolean; /** * The id of the first rule whose conditions all hold, or null when none do. * * Returns a rule id rather than a stage key because the caller needs the * matched rule's `allowClose` before it may move a deal into a closed stage. * * `rules` must arrive already ordered — stage position first, then rule * position within a stage. Sorting needs stage rows this module deliberately * does not see. * * A rule with no conditions never matches: an empty list would otherwise * vacuously AND to true and capture every deal, which is the opposite of what * a half-written rule should do. */ export declare function evaluateStage(rules: EvaluableRule[], fields: Record, mappedFields: Set): string | null; //#endregion 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 CompanyFilters, type CompileOptions, type CompiledView, type Condition, type ConditionOp, 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 EvaluableRule, type ExplainResult, type FilterExpr, type FilterValue, type HostOwnedCompanyField, type HostOwnedContactField, type IntegrationFacts, type IntegrationState, type IntegrationStatus, type IntegrationStep, MATERIALIZING_CONCEPTS, type MaterializingConcept, type MembershipResolver, type MirrorRecord, type NextAction, type Note, OPEN_STAGE_TYPE, PROJECTION_KEY_PATTERN, type Page, type PageRequest, type PerSeatValueSource, type PersistAction, type PipelineConfig, type PipelineKind, type PipelineStageConfig, type ProjectionFieldBinding, type ProjectionIdentity, type ProjectionJoin, type ProjectionMapping, type ProjectionRef, REQUIRED_FIELDS, type RecordSource, type RelationshipCandidate, type RelationshipSpec, type RelationshipVia, type RelationshipsMap, type SourceRow, type SourcedRecord, type StageType, type StepId, type StepProblem, type StepState, type SyncAction, type SyncResult, type SyncStore, type Task, type TaskEntityType, type TaskFilters, type TaskPriority, type TaskStatus, type UpdateCompanyInput, type UpdateContactInput, type UpdateDealInput, type UpdateTaskInput, type ValueInterval, authCustom, buildFilterFromDiscriminator, buildRelationships, compileEntityView, compileEntityViews, defineClivlyConfig, entitiesConfigSchema, explainEntitiesConfig, filterSchema, identityRefs, isClosedStageType, mappingsToEntitiesConfig, missingRequiredFields, projectionFields, projectionSchema, reconcile, relationshipSchema, relationshipViaSignature, relationshipsSchema, requiredFieldsFor, runSync, validateEntitiesConfig };