@clivly/core 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { Activity, Company, CompanyFilters, Contact, ContactFilters, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, Deal, RecordSource, Task, TaskFilters, UpdateCompanyInput, UpdateContactInput, UpdateDealInput, UpdateTaskInput } from "./types.cjs";
1
+ import { Activity, Company, CompanyFilters, Contact, ContactFilters, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, Deal, Page, PageRequest, RecordSource, Task, TaskFilters, UpdateCompanyInput, UpdateContactInput, UpdateDealInput, UpdateTaskInput } from "./types.cjs";
2
2
  //#region src/adapter.d.ts
3
3
  /**
4
4
  * The contract every ORM implementation fulfils. `packages/drizzle` is the first
@@ -58,6 +58,24 @@ export interface CRMAdapter {
58
58
  source?: RecordSource;
59
59
  }): Promise<Deal[]>;
60
60
  getTasks(orgId: string, filters?: TaskFilters): Promise<Task[]>;
61
+ /**
62
+ * One page of companies, newest first.
63
+ *
64
+ * Separate from `getCompanies` rather than a `limit` on `CompanyFilters`,
65
+ * and that is the whole point: an optional field on a shared filter type is
66
+ * silently inheritable, and the callers that must never inherit it are the
67
+ * ones where truncation corrupts data rather than merely hiding rows.
68
+ * `importContacts` builds its dedup set from the unpaged read — a truncated
69
+ * set resurrects a contact archived as a duplicate. A distinct method makes
70
+ * "this call is paginated" a decision at every call site.
71
+ */
72
+ listCompaniesPage(orgId: string, filters?: CompanyFilters, page?: PageRequest): Promise<Page<Company>>;
73
+ /** One page of contacts, newest first. See {@link CRMAdapter.listCompaniesPage}. */
74
+ listContactsPage(orgId: string, filters?: ContactFilters, page?: PageRequest): Promise<Page<Contact>>;
75
+ /** One page of deals, newest first. See {@link CRMAdapter.listCompaniesPage}. */
76
+ listDealsPage(orgId: string, filters?: {
77
+ source?: RecordSource;
78
+ }, page?: PageRequest): Promise<Page<Deal>>;
61
79
  /**
62
80
  * Move a deal to `stage` and reorder it directly after `afterId` (null = top
63
81
  * of the column). The adapter computes the fractional `position` from the
@@ -1,4 +1,4 @@
1
- import { Activity, Company, CompanyFilters, Contact, ContactFilters, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, Deal, RecordSource, Task, TaskFilters, UpdateCompanyInput, UpdateContactInput, UpdateDealInput, UpdateTaskInput } from "./types.mjs";
1
+ import { Activity, Company, CompanyFilters, Contact, ContactFilters, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, Deal, Page, PageRequest, RecordSource, Task, TaskFilters, UpdateCompanyInput, UpdateContactInput, UpdateDealInput, UpdateTaskInput } from "./types.mjs";
2
2
  //#region src/adapter.d.ts
3
3
  /**
4
4
  * The contract every ORM implementation fulfils. `packages/drizzle` is the first
@@ -58,6 +58,24 @@ export interface CRMAdapter {
58
58
  source?: RecordSource;
59
59
  }): Promise<Deal[]>;
60
60
  getTasks(orgId: string, filters?: TaskFilters): Promise<Task[]>;
61
+ /**
62
+ * One page of companies, newest first.
63
+ *
64
+ * Separate from `getCompanies` rather than a `limit` on `CompanyFilters`,
65
+ * and that is the whole point: an optional field on a shared filter type is
66
+ * silently inheritable, and the callers that must never inherit it are the
67
+ * ones where truncation corrupts data rather than merely hiding rows.
68
+ * `importContacts` builds its dedup set from the unpaged read — a truncated
69
+ * set resurrects a contact archived as a duplicate. A distinct method makes
70
+ * "this call is paginated" a decision at every call site.
71
+ */
72
+ listCompaniesPage(orgId: string, filters?: CompanyFilters, page?: PageRequest): Promise<Page<Company>>;
73
+ /** One page of contacts, newest first. See {@link CRMAdapter.listCompaniesPage}. */
74
+ listContactsPage(orgId: string, filters?: ContactFilters, page?: PageRequest): Promise<Page<Contact>>;
75
+ /** One page of deals, newest first. See {@link CRMAdapter.listCompaniesPage}. */
76
+ listDealsPage(orgId: string, filters?: {
77
+ source?: RecordSource;
78
+ }, page?: PageRequest): Promise<Page<Deal>>;
61
79
  /**
62
80
  * Move a deal to `stage` and reorder it directly after `afterId` (null = top
63
81
  * of the column). The adapter computes the fractional `position` from the
package/dist/index.cjs CHANGED
@@ -8,6 +8,34 @@ const require_mappings_config = require("./mappings-config.cjs");
8
8
  const require_view_compiler = require("./view-compiler-DP4Fbj7-.cjs");
9
9
  const require_sync_engine = require("./sync-engine.cjs");
10
10
  const require_types = require("./types.cjs");
11
+ //#region src/deal-value.ts
12
+ const MONTHS_PER_YEAR = 12;
13
+ /** Only two decimal-free seat counts make sense; anything else is bad data. */
14
+ const SEAT_COUNT_REGEX = /^\d+$/;
15
+ /**
16
+ * The value one deal should have, in cents, or null to leave it alone.
17
+ *
18
+ * Returning null rather than 0 for unusable input is deliberate (D33). A
19
+ * renamed column would otherwise zero an entire pipeline silently, and a zeroed
20
+ * board reads as a business problem rather than a configuration problem. The
21
+ * unmapped case is surfaced through the rule-health warning surface instead of
22
+ * being written into the data — the same fail-safe `matchesCondition` applies.
23
+ */
24
+ function computeDealValue(source, fields, mappedFields, stageType) {
25
+ if (source.zeroInStageTypes?.includes(stageType)) return 0;
26
+ if (!(mappedFields.has(source.seatField) && mappedFields.has(source.planField))) return null;
27
+ const planKey = fields[source.planField];
28
+ if (planKey == null || planKey === "") return null;
29
+ const unitPriceCents = source.plans[planKey];
30
+ if (unitPriceCents === void 0) return null;
31
+ const rawSeats = fields[source.seatField];
32
+ if (rawSeats == null || !SEAT_COUNT_REGEX.test(rawSeats)) return null;
33
+ const seats = Number(rawSeats);
34
+ if (!Number.isSafeInteger(seats)) return null;
35
+ const monthly = (source.baseFeeCents ?? 0) + seats * unitPriceCents;
36
+ return source.intervalField !== void 0 && mappedFields.has(source.intervalField) && fields[source.intervalField] === "year" ? Math.round(monthly / MONTHS_PER_YEAR) : monthly;
37
+ }
38
+ //#endregion
11
39
  //#region src/host-owned-fields.ts
12
40
  /**
13
41
  * Which fields a CRM record lets a user edit.
@@ -250,6 +278,7 @@ exports.compileProjectionIdentity = require_view_compiler.compileProjectionIdent
250
278
  exports.compileProjectionJoins = require_view_compiler.compileProjectionJoins;
251
279
  exports.compileProjectionPreview = require_view_compiler.compileProjectionPreview;
252
280
  exports.compileProjectionSelect = require_view_compiler.compileProjectionSelect;
281
+ exports.computeDealValue = computeDealValue;
253
282
  exports.coversUniqueKey = require_schema_meta.coversUniqueKey;
254
283
  exports.defineClivlyConfig = require_entity_config.defineClivlyConfig;
255
284
  exports.entitiesConfigSchema = require_entity_config.entitiesConfigSchema;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Activity, ActivityType, Company, CompanyFilters, Contact, ContactFilters, ContactStatus, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, CrmRole, Deal, DealStage, Note, OPEN_STAGE_TYPE, PipelineConfig, PipelineKind, PipelineStageConfig, RecordSource, StageType, Task, TaskEntityType, TaskFilters, TaskPriority, TaskStatus, UpdateCompanyInput, UpdateContactInput, UpdateDealInput, UpdateTaskInput, ValueInterval, isClosedStageType } from "./types.cjs";
1
+ 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";
2
2
  import { CRMAdapter } from "./adapter.cjs";
3
3
  import { ClivlyAuthAdapter, ClivlyUser, CrmMembership, MembershipResolver, authCustom } from "./auth-adapter.cjs";
4
4
  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";
@@ -7,6 +7,41 @@ import { RelationshipCandidate, buildFilterFromDiscriminator, buildRelationships
7
7
  import { EntityMappingRow, mappingsToEntitiesConfig } from "./mappings-config.cjs";
8
8
  import { CompileOptions, CompiledView, DEFAULT_VIEW_PREFIX, ExplainResult, compileEntityView, compileEntityViews, explainEntitiesConfig } from "./view-compiler.cjs";
9
9
  import { EntitySyncResult, MirrorRecord, PersistAction, SourceRow, SyncAction, SyncResult, SyncStore, reconcile, runSync } from "./sync-engine.cjs";
10
+ //#region src/deal-value.d.ts
11
+ /**
12
+ * Value derived as a flat fee plus a per-seat price that depends on the plan.
13
+ *
14
+ * Every field name is configuration and every plan key is data, so a second
15
+ * subscription business configures this without a code change. The formula is
16
+ * fixed with named slots rather than an expression language — the same choice
17
+ * D8 made for stage-rule conditions.
18
+ */
19
+ interface PerSeatValueSource {
20
+ /** Flat monthly fee in cents, added before the interval is normalised. */
21
+ baseFeeCents?: number;
22
+ /** Mapped column holding "month" or "year". Absent means always monthly. */
23
+ intervalField?: string;
24
+ kind: "per_seat";
25
+ /** Mapped column holding the plan key. */
26
+ planField: string;
27
+ /** Unit price in cents, keyed by the value found in `planField`. */
28
+ plans: Record<string, number>;
29
+ /** Mapped column holding the seat count. */
30
+ seatField: string;
31
+ /** Stage types whose deals are worth nothing (usually `["lost"]`). */
32
+ zeroInStageTypes?: string[];
33
+ }
34
+ /**
35
+ * The value one deal should have, in cents, or null to leave it alone.
36
+ *
37
+ * Returning null rather than 0 for unusable input is deliberate (D33). A
38
+ * renamed column would otherwise zero an entire pipeline silently, and a zeroed
39
+ * board reads as a business problem rather than a configuration problem. The
40
+ * unmapped case is surfaced through the rule-health warning surface instead of
41
+ * being written into the data — the same fail-safe `matchesCondition` applies.
42
+ */
43
+ export declare function computeDealValue(source: PerSeatValueSource, fields: Record<string, string | null>, mappedFields: Set<string>, stageType: string): number | null;
44
+ //#endregion
10
45
  //#region src/host-owned-fields.d.ts
11
46
  /**
12
47
  * Which fields a CRM record lets a user edit.
@@ -298,4 +333,4 @@ export declare function matchesCondition(condition: Condition, fields: Record<st
298
333
  */
299
334
  export declare function evaluateStage(rules: EvaluableRule[], fields: Record<string, string | null>, mappedFields: Set<string>): string | null;
300
335
  //#endregion
301
- 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 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 };
336
+ 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 };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { Activity, ActivityType, Company, CompanyFilters, Contact, ContactFilters, ContactStatus, CreateActivityInput, CreateCompanyInput, CreateContactInput, CreateDealInput, CreateTaskInput, CrmRole, Deal, DealStage, Note, OPEN_STAGE_TYPE, PipelineConfig, PipelineKind, PipelineStageConfig, RecordSource, StageType, Task, TaskEntityType, TaskFilters, TaskPriority, TaskStatus, UpdateCompanyInput, UpdateContactInput, UpdateDealInput, UpdateTaskInput, ValueInterval, isClosedStageType } from "./types.mjs";
1
+ 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.mjs";
2
2
  import { CRMAdapter } from "./adapter.mjs";
3
3
  import { ClivlyAuthAdapter, ClivlyUser, CrmMembership, MembershipResolver, authCustom } from "./auth-adapter.mjs";
4
4
  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.mjs";
@@ -7,6 +7,41 @@ import { RelationshipCandidate, buildFilterFromDiscriminator, buildRelationships
7
7
  import { EntityMappingRow, mappingsToEntitiesConfig } from "./mappings-config.mjs";
8
8
  import { CompileOptions, CompiledView, DEFAULT_VIEW_PREFIX, ExplainResult, compileEntityView, compileEntityViews, explainEntitiesConfig } from "./view-compiler.mjs";
9
9
  import { EntitySyncResult, MirrorRecord, PersistAction, SourceRow, SyncAction, SyncResult, SyncStore, reconcile, runSync } from "./sync-engine.mjs";
10
+ //#region src/deal-value.d.ts
11
+ /**
12
+ * Value derived as a flat fee plus a per-seat price that depends on the plan.
13
+ *
14
+ * Every field name is configuration and every plan key is data, so a second
15
+ * subscription business configures this without a code change. The formula is
16
+ * fixed with named slots rather than an expression language — the same choice
17
+ * D8 made for stage-rule conditions.
18
+ */
19
+ interface PerSeatValueSource {
20
+ /** Flat monthly fee in cents, added before the interval is normalised. */
21
+ baseFeeCents?: number;
22
+ /** Mapped column holding "month" or "year". Absent means always monthly. */
23
+ intervalField?: string;
24
+ kind: "per_seat";
25
+ /** Mapped column holding the plan key. */
26
+ planField: string;
27
+ /** Unit price in cents, keyed by the value found in `planField`. */
28
+ plans: Record<string, number>;
29
+ /** Mapped column holding the seat count. */
30
+ seatField: string;
31
+ /** Stage types whose deals are worth nothing (usually `["lost"]`). */
32
+ zeroInStageTypes?: string[];
33
+ }
34
+ /**
35
+ * The value one deal should have, in cents, or null to leave it alone.
36
+ *
37
+ * Returning null rather than 0 for unusable input is deliberate (D33). A
38
+ * renamed column would otherwise zero an entire pipeline silently, and a zeroed
39
+ * board reads as a business problem rather than a configuration problem. The
40
+ * unmapped case is surfaced through the rule-health warning surface instead of
41
+ * being written into the data — the same fail-safe `matchesCondition` applies.
42
+ */
43
+ export declare function computeDealValue(source: PerSeatValueSource, fields: Record<string, string | null>, mappedFields: Set<string>, stageType: string): number | null;
44
+ //#endregion
10
45
  //#region src/host-owned-fields.d.ts
11
46
  /**
12
47
  * Which fields a CRM record lets a user edit.
@@ -298,4 +333,4 @@ export declare function matchesCondition(condition: Condition, fields: Record<st
298
333
  */
299
334
  export declare function evaluateStage(rules: EvaluableRule[], fields: Record<string, string | null>, mappedFields: Set<string>): string | null;
300
335
  //#endregion
301
- 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 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 };
336
+ 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 };
package/dist/index.mjs CHANGED
@@ -7,6 +7,34 @@ import { mappingsToEntitiesConfig } from "./mappings-config.mjs";
7
7
  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-CLvZ0wJW.mjs";
8
8
  import { reconcile, runSync } from "./sync-engine.mjs";
9
9
  import { OPEN_STAGE_TYPE, isClosedStageType } from "./types.mjs";
10
+ //#region src/deal-value.ts
11
+ const MONTHS_PER_YEAR = 12;
12
+ /** Only two decimal-free seat counts make sense; anything else is bad data. */
13
+ const SEAT_COUNT_REGEX = /^\d+$/;
14
+ /**
15
+ * The value one deal should have, in cents, or null to leave it alone.
16
+ *
17
+ * Returning null rather than 0 for unusable input is deliberate (D33). A
18
+ * renamed column would otherwise zero an entire pipeline silently, and a zeroed
19
+ * board reads as a business problem rather than a configuration problem. The
20
+ * unmapped case is surfaced through the rule-health warning surface instead of
21
+ * being written into the data — the same fail-safe `matchesCondition` applies.
22
+ */
23
+ function computeDealValue(source, fields, mappedFields, stageType) {
24
+ if (source.zeroInStageTypes?.includes(stageType)) return 0;
25
+ if (!(mappedFields.has(source.seatField) && mappedFields.has(source.planField))) return null;
26
+ const planKey = fields[source.planField];
27
+ if (planKey == null || planKey === "") return null;
28
+ const unitPriceCents = source.plans[planKey];
29
+ if (unitPriceCents === void 0) return null;
30
+ const rawSeats = fields[source.seatField];
31
+ if (rawSeats == null || !SEAT_COUNT_REGEX.test(rawSeats)) return null;
32
+ const seats = Number(rawSeats);
33
+ if (!Number.isSafeInteger(seats)) return null;
34
+ const monthly = (source.baseFeeCents ?? 0) + seats * unitPriceCents;
35
+ return source.intervalField !== void 0 && mappedFields.has(source.intervalField) && fields[source.intervalField] === "year" ? Math.round(monthly / MONTHS_PER_YEAR) : monthly;
36
+ }
37
+ //#endregion
10
38
  //#region src/host-owned-fields.ts
11
39
  /**
12
40
  * Which fields a CRM record lets a user edit.
@@ -228,4 +256,4 @@ function evaluateStage(rules, fields, mappedFields) {
228
256
  return null;
229
257
  }
230
258
  //#endregion
231
- export { BASE_REF, CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, DEFAULT_VIEW_PREFIX, MATERIALIZING_CONCEPTS, OPEN_STAGE_TYPE, PREVIEW_MAX_ROWS, PROJECTION_KEY_PATTERN, REQUIRED_FIELDS, authCustom, buildFilterFromDiscriminator, buildRelationships, columnMetaFor, compileEntityView, compileEntityViews, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, compileProjectionPreview, compileProjectionSelect, coversUniqueKey, defineClivlyConfig, entitiesConfigSchema, evaluateStage, explainEntitiesConfig, fanOutJoins, filterSchema, hasRichMeta, hostOwnedFieldMessage, identityRefs, isClosedStageType, isHostOwned, joinFansOut, mappingsToEntitiesConfig, matchesCondition, missingRequiredFields, primaryKeyColumns, projectionFields, projectionSchema, quoteIdent, reconcile, rejectedCompanyFields, rejectedContactFields, relationshipSchema, relationshipViaSignature, relationshipsSchema, requiredFieldsFor, runSync, slugify, toObjectTypeSlug, validateEntitiesConfig, validateProjection, validateProjectionFilter };
259
+ export { BASE_REF, CANONICAL_FIELDS, CRM_CONCEPTS, ClivlyConfigError, DEFAULT_VIEW_PREFIX, MATERIALIZING_CONCEPTS, OPEN_STAGE_TYPE, PREVIEW_MAX_ROWS, PROJECTION_KEY_PATTERN, REQUIRED_FIELDS, authCustom, buildFilterFromDiscriminator, buildRelationships, columnMetaFor, compileEntityView, compileEntityViews, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, compileProjectionPreview, compileProjectionSelect, computeDealValue, coversUniqueKey, defineClivlyConfig, entitiesConfigSchema, evaluateStage, explainEntitiesConfig, fanOutJoins, filterSchema, hasRichMeta, hostOwnedFieldMessage, identityRefs, isClosedStageType, isHostOwned, joinFansOut, mappingsToEntitiesConfig, matchesCondition, missingRequiredFields, primaryKeyColumns, projectionFields, projectionSchema, quoteIdent, reconcile, rejectedCompanyFields, rejectedContactFields, relationshipSchema, relationshipViaSignature, relationshipsSchema, requiredFieldsFor, runSync, slugify, toObjectTypeSlug, validateEntitiesConfig, validateProjection, validateProjectionFilter };
package/dist/types.d.cts CHANGED
@@ -92,7 +92,21 @@ export interface Deal {
92
92
  owner: string | null;
93
93
  position: number | null;
94
94
  stage: DealStage;
95
+ /**
96
+ * When a human last moved this deal by hand. Non-null means stage derivation
97
+ * no longer touches it, so a board must be able to mark it as overridden and
98
+ * offer a way back — otherwise an overridden deal is indistinguishable from
99
+ * one the rules simply never matched.
100
+ */
101
+ stageOverrideAt: Date | null;
95
102
  value: number;
103
+ /**
104
+ * When a human last set this deal's value by hand. Non-null means value
105
+ * derivation no longer touches it. Mirrors {@link Deal.stageOverrideAt} — and
106
+ * needs the same visibility, since an honoured-but-invisible override reads
107
+ * as a stuck deal rather than a pinned one.
108
+ */
109
+ valueOverrideAt: Date | null;
96
110
  }
97
111
  export interface Task {
98
112
  archivedAt: Date | null;
@@ -234,6 +248,28 @@ export interface ContactFilters {
234
248
  source?: RecordSource;
235
249
  status?: ContactStatus;
236
250
  }
251
+ /**
252
+ * One page of a keyset-paginated read.
253
+ *
254
+ * Keyset, not offset: `LIMIT/OFFSET` re-scans every skipped row, so page 200
255
+ * costs 200 pages of work, and a row inserted mid-listing shifts everything
256
+ * after it — the reader sees a record twice or never. A cursor anchored to
257
+ * `(createdAt, id)` is stable under concurrent writes and costs the same at
258
+ * page 1 and page 200.
259
+ *
260
+ * `audit.list` already does this; these types generalise its shape so the CRM
261
+ * lists cannot drift into a second convention.
262
+ */
263
+ export interface PageRequest {
264
+ /** Opaque `${createdAtISO}|${id}`. Rows strictly older than this. */
265
+ cursor?: string;
266
+ limit?: number;
267
+ }
268
+ export interface Page<T> {
269
+ /** Null when this is the last page. */
270
+ nextCursor: string | null;
271
+ rows: T[];
272
+ }
237
273
  export interface CompanyFilters {
238
274
  /** See {@link ContactFilters.excludeArchived}. */
239
275
  excludeArchived?: boolean;
package/dist/types.d.mts CHANGED
@@ -92,7 +92,21 @@ export interface Deal {
92
92
  owner: string | null;
93
93
  position: number | null;
94
94
  stage: DealStage;
95
+ /**
96
+ * When a human last moved this deal by hand. Non-null means stage derivation
97
+ * no longer touches it, so a board must be able to mark it as overridden and
98
+ * offer a way back — otherwise an overridden deal is indistinguishable from
99
+ * one the rules simply never matched.
100
+ */
101
+ stageOverrideAt: Date | null;
95
102
  value: number;
103
+ /**
104
+ * When a human last set this deal's value by hand. Non-null means value
105
+ * derivation no longer touches it. Mirrors {@link Deal.stageOverrideAt} — and
106
+ * needs the same visibility, since an honoured-but-invisible override reads
107
+ * as a stuck deal rather than a pinned one.
108
+ */
109
+ valueOverrideAt: Date | null;
96
110
  }
97
111
  export interface Task {
98
112
  archivedAt: Date | null;
@@ -234,6 +248,28 @@ export interface ContactFilters {
234
248
  source?: RecordSource;
235
249
  status?: ContactStatus;
236
250
  }
251
+ /**
252
+ * One page of a keyset-paginated read.
253
+ *
254
+ * Keyset, not offset: `LIMIT/OFFSET` re-scans every skipped row, so page 200
255
+ * costs 200 pages of work, and a row inserted mid-listing shifts everything
256
+ * after it — the reader sees a record twice or never. A cursor anchored to
257
+ * `(createdAt, id)` is stable under concurrent writes and costs the same at
258
+ * page 1 and page 200.
259
+ *
260
+ * `audit.list` already does this; these types generalise its shape so the CRM
261
+ * lists cannot drift into a second convention.
262
+ */
263
+ export interface PageRequest {
264
+ /** Opaque `${createdAtISO}|${id}`. Rows strictly older than this. */
265
+ cursor?: string;
266
+ limit?: number;
267
+ }
268
+ export interface Page<T> {
269
+ /** Null when this is the last page. */
270
+ nextCursor: string | null;
271
+ rows: T[];
272
+ }
237
273
  export interface CompanyFilters {
238
274
  /** See {@link ContactFilters.excludeArchived}. */
239
275
  excludeArchived?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clivly/core",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "private": false,
5
5
  "description": "ORM/auth-agnostic CRM adapter contracts and shared domain types for Clivly.",
6
6
  "license": "MIT",