clivly 0.9.7 → 0.10.1

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.
Files changed (136) hide show
  1. package/README.md +9 -9
  2. package/{adapter-C2eRbBfo.d.cts → adapter-1TN0zohk.d.cts} +1 -1
  3. package/{adapter-D5LRa-NA.d.mts → adapter-uxirB88i.d.mts} +1 -1
  4. package/{auth-adapter-Bjgo8LhN.d.mts → auth-adapter-CXTiW3Mb.d.cts} +1 -1
  5. package/{auth-adapter-DWzgl5Yq.d.cts → auth-adapter-DSuYwv1c.d.mts} +1 -1
  6. package/auth-better-auth.d.cts +1 -1
  7. package/auth-better-auth.d.mts +1 -1
  8. package/auth-clerk.d.cts +1 -1
  9. package/auth-clerk.d.mts +1 -1
  10. package/auth-workos.d.cts +1 -1
  11. package/auth-workos.d.mts +1 -1
  12. package/core-adapter.d.cts +1 -1
  13. package/core-adapter.d.mts +1 -1
  14. package/core-auth-adapter.d.cts +1 -1
  15. package/core-auth-adapter.d.mts +1 -1
  16. package/core-drizzle.d.cts +1 -1
  17. package/core-drizzle.d.mts +1 -1
  18. package/core-entity-config.cjs +1 -1
  19. package/core-entity-config.d.cts +1 -1
  20. package/core-entity-config.d.mts +1 -1
  21. package/core-entity-config.mjs +1 -1
  22. package/core-entity-heuristics.cjs +1 -0
  23. package/core-entity-heuristics.d.cts +3 -3
  24. package/core-entity-heuristics.d.mts +3 -3
  25. package/core-entity-heuristics.mjs +2 -2
  26. package/core-mapping-form.d.cts +1 -1
  27. package/core-mapping-form.d.mts +1 -1
  28. package/core-mappings-config.cjs +1 -1
  29. package/core-mappings-config.d.cts +1 -1
  30. package/core-mappings-config.d.mts +1 -1
  31. package/core-mappings-config.mjs +1 -1
  32. package/core-sync-engine.cjs +1 -1
  33. package/core-sync-engine.d.cts +1 -1
  34. package/core-sync-engine.d.mts +1 -1
  35. package/core-sync-engine.mjs +1 -1
  36. package/core-types.cjs +4 -0
  37. package/core-types.d.cts +2 -2
  38. package/core-types.d.mts +2 -2
  39. package/core-types.mjs +2 -1
  40. package/core-view-compiler.cjs +1 -1
  41. package/core-view-compiler.d.cts +1 -1
  42. package/core-view-compiler.d.mts +1 -1
  43. package/core-view-compiler.mjs +1 -1
  44. package/core.cjs +17 -15
  45. package/core.d.cts +10 -10
  46. package/core.d.mts +10 -10
  47. package/core.mjs +9 -8
  48. package/{drizzle-BmDDgAIF.d.mts → drizzle-DTOuGXEn.d.cts} +1 -1
  49. package/{drizzle-ixVV4pPN.d.cts → drizzle-SGapoly2.d.mts} +1 -1
  50. package/drizzle.cjs +2 -2
  51. package/drizzle.d.cts +3 -3
  52. package/drizzle.d.mts +3 -3
  53. package/drizzle.mjs +2 -2
  54. package/{entity-config-C8IIkrmX.mjs → entity-config-C4d3K9CP.mjs} +81 -4
  55. package/{entity-config-B2RA53nw.d.cts → entity-config-DNQAMbMS.d.cts} +27 -1
  56. package/{entity-config-B2RA53nw.d.mts → entity-config-DNQAMbMS.d.mts} +27 -1
  57. package/{entity-config-CPM-Tp-d.cjs → entity-config-DgAe0_YD.cjs} +86 -3
  58. package/index.cjs +24 -14
  59. package/index.mjs +24 -14
  60. package/internal-BAOG5eE4.d.mts +226 -0
  61. package/internal-BHJ0X3fC.d.cts +226 -0
  62. package/internal-Bgiimp5D.mjs +144 -0
  63. package/internal-Cz59xMj8.cjs +167 -0
  64. package/internal-adapter.d.cts +1 -1
  65. package/internal-adapter.d.mts +1 -1
  66. package/internal-auth-adapter.d.cts +1 -1
  67. package/internal-auth-adapter.d.mts +1 -1
  68. package/internal-drizzle.d.cts +1 -1
  69. package/internal-drizzle.d.mts +1 -1
  70. package/internal-entity-config.cjs +1 -1
  71. package/internal-entity-config.d.cts +1 -1
  72. package/internal-entity-config.d.mts +1 -1
  73. package/internal-entity-config.mjs +1 -1
  74. package/internal-entity-heuristics.cjs +34 -1
  75. package/internal-entity-heuristics.d.cts +8 -2
  76. package/internal-entity-heuristics.d.mts +8 -2
  77. package/internal-entity-heuristics.mjs +34 -2
  78. package/internal-mapping-form.d.cts +1 -1
  79. package/internal-mapping-form.d.mts +1 -1
  80. package/internal-mappings-config.cjs +1 -1
  81. package/internal-mappings-config.d.cts +1 -1
  82. package/internal-mappings-config.d.mts +1 -1
  83. package/internal-mappings-config.mjs +1 -1
  84. package/internal-sync-engine.cjs +1 -1
  85. package/internal-sync-engine.d.cts +1 -1
  86. package/internal-sync-engine.d.mts +1 -1
  87. package/internal-sync-engine.mjs +1 -1
  88. package/internal-types.cjs +4 -0
  89. package/internal-types.d.cts +2 -2
  90. package/internal-types.d.mts +2 -2
  91. package/internal-types.mjs +2 -1
  92. package/internal-view-compiler.cjs +1 -1
  93. package/internal-view-compiler.d.cts +1 -1
  94. package/internal-view-compiler.d.mts +1 -1
  95. package/internal-view-compiler.mjs +1 -1
  96. package/internal.cjs +17 -15
  97. package/internal.d.cts +10 -10
  98. package/internal.d.mts +10 -10
  99. package/internal.mjs +9 -8
  100. package/{mapping-form-DH77BXLv.d.mts → mapping-form-Cc7Ewv8c.d.mts} +1 -1
  101. package/{mapping-form-J0aU6O7l.d.cts → mapping-form-DwdbYAfR.d.cts} +1 -1
  102. package/{mappings-config-D9dsonoi.d.mts → mappings-config-Be9D43Np.d.mts} +1 -1
  103. package/{mappings-config-RQLGx8ub.d.cts → mappings-config-DUt4-9iO.d.cts} +1 -1
  104. package/{mappings-config-CBwL3FVh.cjs → mappings-config-Dx9ldgdf.cjs} +2 -16
  105. package/{mappings-config-DUKBe6OS.mjs → mappings-config-MoXBeI8p.mjs} +1 -15
  106. package/{namespace-probe-BQ4imnV7.d.mts → namespace-probe-9ScMDR0t.d.mts} +1 -1
  107. package/{namespace-probe-DZIGdvlm.d.cts → namespace-probe-DC4tbmLj.d.cts} +1 -1
  108. package/package.json +1 -1
  109. package/{projection-hnPSgH6G.mjs → projection-CSGJbZlZ.mjs} +1 -1
  110. package/{projection-DEwL-D8R.cjs → projection-Dz6_i1-w.cjs} +0 -6
  111. package/{projection-sql-iHy-Cx6V.cjs → projection-sql-CbXCKt3s.cjs} +1 -1
  112. package/{projection-sql-9zzTVGvf.mjs → projection-sql-iHoiYr1R.mjs} +1 -1
  113. package/{sdk-BL9BCzyD.cjs → sdk-B1vX4kUJ.cjs} +26 -6
  114. package/{sdk-CubnsyD9.mjs → sdk-D6gX0TXS.mjs} +26 -6
  115. package/sdk.cjs +2 -2
  116. package/sdk.d.cts +2 -2
  117. package/sdk.d.mts +2 -2
  118. package/sdk.mjs +2 -2
  119. package/{sync-engine-BLgwLpR6.cjs → sync-engine-B0FT1ax6.cjs} +1 -1
  120. package/{sync-engine-B_IzoUSx.mjs → sync-engine-CTSIcLlZ.mjs} +1 -1
  121. package/{sync-engine-BG2O1an2.d.cts → sync-engine-Df0kHAoJ.d.cts} +2 -2
  122. package/{sync-engine-DgQbrJON.d.mts → sync-engine-ugFacJ6G.d.mts} +2 -2
  123. package/{types-B1oRXdNP.d.mts → types-Cq1uHUKp.d.cts} +28 -2
  124. package/{types-B1oRXdNP.d.cts → types-Cq1uHUKp.d.mts} +28 -2
  125. package/types-Cq5gHCTq.cjs +20 -0
  126. package/types-YJTkXKzU.mjs +9 -0
  127. package/{view-compiler-D_6gfXFr.mjs → view-compiler-BzKk8nY3.mjs} +2 -2
  128. package/{view-compiler-Bo7pqXW3.d.mts → view-compiler-D3zp9S3K.d.cts} +1 -1
  129. package/{view-compiler-CmEdAMwj.d.cts → view-compiler-TpzDTu7z.d.mts} +1 -1
  130. package/{view-compiler-BHI5E4QI.cjs → view-compiler-ZKZbd3SK.cjs} +2 -2
  131. package/vite.d.cts +1 -1
  132. package/vite.d.mts +1 -1
  133. package/internal-BLKMnIIZ.cjs +0 -259
  134. package/internal-BpVh_J77.mjs +0 -212
  135. package/internal-CvlFk3KC.d.mts +0 -180
  136. package/internal-DGUOLQAq.d.cts +0 -180
package/drizzle.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_projection = require("./projection-DEwL-D8R.cjs");
2
+ const require_projection = require("./projection-Dz6_i1-w.cjs");
3
3
  const require_drizzle_meta = require("./drizzle-meta-Bb1JWYqf.cjs");
4
- const require_projection_sql = require("./projection-sql-iHy-Cx6V.cjs");
4
+ const require_projection_sql = require("./projection-sql-CbXCKt3s.cjs");
5
5
  const require_drizzle = require("./drizzle-DcB7OfBp.cjs");
6
6
  let drizzle_orm = require("drizzle-orm");
7
7
  //#region ../sdk/src/drizzle-projection.ts
package/drizzle.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { F as ProjectionRef, P as ProjectionMapping } from "./entity-config-B2RA53nw.cjs";
2
- import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-ixVV4pPN.cjs";
3
- import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-DZIGdvlm.cjs";
1
+ import { F as ProjectionMapping, I as ProjectionRef } from "./entity-config-DNQAMbMS.cjs";
2
+ import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-DTOuGXEn.cjs";
3
+ import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-DC4tbmLj.cjs";
4
4
  import { Table } from "drizzle-orm";
5
5
  //#region ../sdk/src/drizzle-projection.d.ts
6
6
  /**
package/drizzle.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { F as ProjectionRef, P as ProjectionMapping } from "./entity-config-B2RA53nw.mjs";
2
- import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-BmDDgAIF.mjs";
3
- import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-BQ4imnV7.mjs";
1
+ import { F as ProjectionMapping, I as ProjectionRef } from "./entity-config-DNQAMbMS.mjs";
2
+ import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-SGapoly2.mjs";
3
+ import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-9ScMDR0t.mjs";
4
4
  import { Table } from "drizzle-orm";
5
5
  //#region ../sdk/src/drizzle-projection.d.ts
6
6
  /**
package/drizzle.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { t as BASE_REF } from "./projection-hnPSgH6G.mjs";
1
+ import { t as BASE_REF } from "./projection-CSGJbZlZ.mjs";
2
2
  import { t as DRIZZLE_META } from "./drizzle-meta-D-5k3n0i.mjs";
3
- import { a as compileProjectionPreview, i as compileProjectionJoins, n as compileProjectionFrom, r as compileProjectionIdentity, s as quoteIdent } from "./projection-sql-9zzTVGvf.mjs";
3
+ import { a as compileProjectionPreview, i as compileProjectionJoins, n as compileProjectionFrom, r as compileProjectionIdentity, s as quoteIdent } from "./projection-sql-iHoiYr1R.mjs";
4
4
  import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-wbcuG-cy.mjs";
5
5
  import { asc, getTableColumns, getTableName, sql } from "drizzle-orm";
6
6
  //#region ../sdk/src/drizzle-projection.ts
@@ -1,4 +1,4 @@
1
- import { a as projectionSchema, c as boolean, d as record, f as strictObject, i as identityRefs, l as literal, m as union, o as _enum, p as string, s as array, t as BASE_REF, u as number } from "./projection-hnPSgH6G.mjs";
1
+ import { a as _enum, c as literal, d as strictObject, f as string, i as projectionSchema, l as number, o as array, p as union, r as identityRefs, s as boolean, t as BASE_REF, u as record } from "./projection-CSGJbZlZ.mjs";
2
2
  //#region ../core/src/schema-meta.ts
3
3
  /**
4
4
  * Accessors over a discovered table's schema-v2 metadata.
@@ -29,7 +29,17 @@ function coversUniqueKey(table, columns) {
29
29
  const present = new Set(columns);
30
30
  return (table.uniqueConstraints ?? []).some((key) => key.length > 0 && key.every((column) => present.has(column)));
31
31
  }
32
- /** Is this table's payload rich enough to validate a projection against? */
32
+ /**
33
+ * Is this table's payload rich enough to validate a projection against?
34
+ *
35
+ * A type predicate rather than a plain boolean so the narrowing is visible to
36
+ * the compiler: callers that guard on this can iterate `columnsMeta` directly
37
+ * instead of casting, and a cast that outlives its guard becomes a type error
38
+ * rather than a silent walk over `undefined`.
39
+ *
40
+ * The invariant is unchanged: this reports whether the metadata is *known*,
41
+ * never whether the table has keys. Absent metadata stays UNKNOWN.
42
+ */
33
43
  function hasRichMeta(table) {
34
44
  return Boolean(table.columnsMeta && table.uniqueConstraints);
35
45
  }
@@ -273,6 +283,64 @@ function checkFanOut(errors, projection, resolver, base, basePk) {
273
283
  });
274
284
  }
275
285
  /**
286
+ * One error naming a filter column that is not on the projection's base table,
287
+ * and the base table it would have to be on.
288
+ */
289
+ function columnOnBase(column, baseTable, path) {
290
+ if (baseTable?.columns.includes(column)) return [];
291
+ return [{
292
+ path,
293
+ message: `filter column "${column}" does not exist on "${baseTable?.name ?? "the base table"}" — a derived object is filtered by its base table's columns only, so name one of those, or move the condition into a join's \`on\``
294
+ }];
295
+ }
296
+ /**
297
+ * A derived object's filter is evaluated in the cloud against pushed rows, so
298
+ * it can only name columns of the base table.
299
+ *
300
+ * Every rejected shape here fails *silently* today, which is the whole reason
301
+ * the check exists — a filter that cannot be honoured must error, never quietly
302
+ * return the wrong rows. The three shapes fail in different directions:
303
+ *
304
+ * - `$raw` is opaque SQL meant for the embedded model. The cloud path cannot
305
+ * evaluate it and passes every row, so the filter is ignored and the object
306
+ * syncs UNFILTERED.
307
+ * - A joined-table column resolves to nothing on a pushed row, so under a
308
+ * positive operator (`eq`, `in`) no row matches and the object syncs EMPTY.
309
+ * - The same unresolvable column under a NEGATIVE operator (`ne`, `isNull`,
310
+ * `notIn`) is the dangerous one: the missing cell reads as null, so the
311
+ * predicate is true for every row and a filter meant to narrow the sync
312
+ * ADMITS EVERYTHING. Nobody notices extra rows arriving.
313
+ *
314
+ * Rejecting an unresolvable column closes the last two together.
315
+ */
316
+ function validateProjectionFilter(filter, projection, schema, base) {
317
+ if (!filter) return [];
318
+ const path = `${base}.filter`;
319
+ if ("$raw" in filter) return [{
320
+ path,
321
+ message: "a $raw filter cannot be evaluated against a derived object's rows — it is opaque SQL, so the cloud passes every row through unfiltered. Express it as column conditions on the base table instead"
322
+ }];
323
+ const baseTable = schema.find((t) => t.name === projection.baseTable);
324
+ const errors = [];
325
+ for (const column of Object.keys(filter)) {
326
+ if (column.includes(".")) {
327
+ const [ref, ...rest] = column.split(".");
328
+ const bare = rest.join(".");
329
+ if (ref !== "base") {
330
+ errors.push({
331
+ path,
332
+ message: `filter column "${column}" names the join "${ref}", but a derived object is filtered by its base table ("${projection.baseTable}") only — filter on a base column, or move the condition into that join's \`on\``
333
+ });
334
+ continue;
335
+ }
336
+ errors.push(...columnOnBase(bare, baseTable, path));
337
+ continue;
338
+ }
339
+ errors.push(...columnOnBase(column, baseTable, path));
340
+ }
341
+ return errors;
342
+ }
343
+ /**
276
344
  * Validate a projection against the discovered schema. Returns every error
277
345
  * found (not just the first) so a builder UI can show them all at once.
278
346
  */
@@ -537,6 +605,15 @@ function customObjectDeclarations(config) {
537
605
  * Qualified rather than bare: two bindings can pull the same column name from
538
606
  * different tables (`cl.full_name` and `adv.full_name`), which a bare column
539
607
  * name cannot tell apart.
608
+ *
609
+ * This is the single derivation of that map, and both consumers depend on it
610
+ * emitting ref tokens. `mappingsToEntitiesConfig` populates a projection
611
+ * entity's required `fields`; the cloud ingest path needs the same map because
612
+ * a projection's persisted `crm_entity_mappings.field_map` is empty by design,
613
+ * so `rowToSourceRow` derives the effective field map from the projection
614
+ * instead. The tokens emitted here are exactly how `fromDrizzleProjection` keys
615
+ * the rows it pushes (`drizzle-projection.ts` `refToken`), so the two line up.
616
+ * Deriving this a second time anywhere would let the copies drift.
540
617
  */
541
618
  function projectionFields(projection) {
542
619
  const fields = {};
@@ -675,7 +752,7 @@ function validateEntity(v, config, key, entity, schema) {
675
752
  if (entity.source !== entity.projection.baseTable) v.report(`${base}.source`, `"source" ("${entity.source}") must match "projection.baseTable" ("${entity.projection.baseTable}") — the view compiler reads baseTable and the sync engine reads source, so a mismatch makes them disagree about which table this entity is`);
676
753
  for (const error of validateProjection(entity.projection, schema, base)) v.report(error.path, error.message);
677
754
  validateReadOnly(v, base, entity);
678
- validateFilter(v, base, entity);
755
+ for (const error of validateProjectionFilter(entity.filter, entity.projection, schema, base)) v.report(error.path, error.message);
679
756
  return;
680
757
  }
681
758
  validateFields(v, base, entity);
@@ -701,4 +778,4 @@ function validateEntitiesConfig(config, schema) {
701
778
  };
702
779
  }
703
780
  //#endregion
704
- export { columnMetaFor as _, MATERIALIZING_CONCEPTS as a, primaryKeyColumns as b, entitiesConfigSchema as c, relationshipSchema as d, relationshipsSchema as f, joinFansOut as g, fanOutJoins as h, ClivlyConfigError as i, filterSchema as l, validateProjection as m, CRM_CONCEPTS as n, customObjectDeclarations as o, validateEntitiesConfig as p, CUSTOM_CONCEPT as r, defineClivlyConfig as s, CANONICAL_FIELDS as t, projectionFields as u, coversUniqueKey as v, hasRichMeta as y };
781
+ export { joinFansOut as _, MATERIALIZING_CONCEPTS as a, hasRichMeta as b, entitiesConfigSchema as c, relationshipSchema as d, relationshipsSchema as f, fanOutJoins as g, validateProjectionFilter as h, ClivlyConfigError as i, filterSchema as l, validateProjection as m, CRM_CONCEPTS as n, customObjectDeclarations as o, validateEntitiesConfig as p, CUSTOM_CONCEPT as r, defineClivlyConfig as s, CANONICAL_FIELDS as t, projectionFields as u, columnMetaFor as v, primaryKeyColumns as x, coversUniqueKey as y };
@@ -2137,6 +2137,23 @@ declare const ZodReadonly: $constructor<ZodReadonly>;
2137
2137
  */
2138
2138
  /** The reserved ref name for the projection's base table. */
2139
2139
  declare const BASE_REF = "base";
2140
+ /**
2141
+ * Entity keys become view names (`crm_src_<entityKey>`), so keep them slugs.
2142
+ *
2143
+ * Accepts hyphens as well as underscores, matching `OBJECT_TYPE_SLUG_PATTERN`
2144
+ * on the server. Those two were widened apart: the server learned hyphens
2145
+ * because the SDK's own `slugify` collapses non-alphanumerics to them and
2146
+ * `clivly status` reports that form as canonical — but this pattern was missed,
2147
+ * so `clivly status` would tell you to key an entity `client-accounts` and then
2148
+ * `defineClivlyConfig` would reject the config it had just asked for. Same bug,
2149
+ * opposite half of the product.
2150
+ *
2151
+ * Widening is the safe direction: every key that was valid before still is, so
2152
+ * nothing existing is renamed. Safe at all because neither use is ever a bare
2153
+ * SQL identifier — the view name is quoted (`view-compiler.ts`, `quoteIdent`),
2154
+ * and so is a join's `key` when it is emitted as an alias.
2155
+ */
2156
+ declare const PROJECTION_KEY_PATTERN: RegExp;
2140
2157
  declare const projectionRefSchema: ZodObject<{
2141
2158
  table: ZodString;
2142
2159
  column: ZodString;
@@ -2638,6 +2655,15 @@ declare function customObjectDeclarations(config: ClivlyEntitiesConfig): CustomO
2638
2655
  * Qualified rather than bare: two bindings can pull the same column name from
2639
2656
  * different tables (`cl.full_name` and `adv.full_name`), which a bare column
2640
2657
  * name cannot tell apart.
2658
+ *
2659
+ * This is the single derivation of that map, and both consumers depend on it
2660
+ * emitting ref tokens. `mappingsToEntitiesConfig` populates a projection
2661
+ * entity's required `fields`; the cloud ingest path needs the same map because
2662
+ * a projection's persisted `crm_entity_mappings.field_map` is empty by design,
2663
+ * so `rowToSourceRow` derives the effective field map from the projection
2664
+ * instead. The tokens emitted here are exactly how `fromDrizzleProjection` keys
2665
+ * the rows it pushes (`drizzle-projection.ts` `refToken`), so the two line up.
2666
+ * Deriving this a second time anywhere would let the copies drift.
2641
2667
  */
2642
2668
  declare function projectionFields(projection: ProjectionMapping): Record<string, string>;
2643
2669
  declare function defineClivlyConfig(config: ClivlyEntitiesConfigInput, options?: {
@@ -2698,4 +2724,4 @@ interface ConfigValidationResult {
2698
2724
  */
2699
2725
  declare function validateEntitiesConfig(config: ClivlyEntitiesConfig, schema: DiscoveredSchemaTable[]): ConfigValidationResult;
2700
2726
  //#endregion
2701
- export { BASE_REF as A, defineClivlyConfig as C, relationshipSchema as D, projectionFields as E, ProjectionRef as F, identityRefs as I, projectionSchema as L, ProjectionIdentity as M, ProjectionJoin as N, relationshipsSchema as O, ProjectionMapping as P, customObjectDeclarations as S, filterSchema as T, MATERIALIZING_CONCEPTS as _, ClivlyEntitiesConfig as a, RelationshipVia as b, ClivlyEntityConfigInput as c, CrmConcept as d, CustomObjectDeclaration as f, FilterValue as g, FilterExpr as h, ClivlyConfigError as i, ProjectionFieldBinding as j, validateEntitiesConfig as k, ConfigValidationError as l, DiscoveredSchemaTable as m, CRM_CONCEPTS as n, ClivlyEntitiesConfigInput as o, DiscoveredColumnMeta as p, CUSTOM_CONCEPT as r, ClivlyEntityConfig as s, CANONICAL_FIELDS as t, ConfigValidationResult as u, MaterializingConcept as v, entitiesConfigSchema as w, RelationshipsMap as x, RelationshipSpec as y };
2727
+ export { BASE_REF as A, defineClivlyConfig as C, relationshipSchema as D, projectionFields as E, ProjectionMapping as F, ProjectionRef as I, identityRefs as L, ProjectionFieldBinding as M, ProjectionIdentity as N, relationshipsSchema as O, ProjectionJoin as P, projectionSchema as R, customObjectDeclarations as S, filterSchema as T, MATERIALIZING_CONCEPTS as _, ClivlyEntitiesConfig as a, RelationshipVia as b, ClivlyEntityConfigInput as c, CrmConcept as d, CustomObjectDeclaration as f, FilterValue as g, FilterExpr as h, ClivlyConfigError as i, PROJECTION_KEY_PATTERN as j, validateEntitiesConfig as k, ConfigValidationError as l, DiscoveredSchemaTable as m, CRM_CONCEPTS as n, ClivlyEntitiesConfigInput as o, DiscoveredColumnMeta as p, CUSTOM_CONCEPT as r, ClivlyEntityConfig as s, CANONICAL_FIELDS as t, ConfigValidationResult as u, MaterializingConcept as v, entitiesConfigSchema as w, RelationshipsMap as x, RelationshipSpec as y };
@@ -2137,6 +2137,23 @@ declare const ZodReadonly: $constructor<ZodReadonly>;
2137
2137
  */
2138
2138
  /** The reserved ref name for the projection's base table. */
2139
2139
  declare const BASE_REF = "base";
2140
+ /**
2141
+ * Entity keys become view names (`crm_src_<entityKey>`), so keep them slugs.
2142
+ *
2143
+ * Accepts hyphens as well as underscores, matching `OBJECT_TYPE_SLUG_PATTERN`
2144
+ * on the server. Those two were widened apart: the server learned hyphens
2145
+ * because the SDK's own `slugify` collapses non-alphanumerics to them and
2146
+ * `clivly status` reports that form as canonical — but this pattern was missed,
2147
+ * so `clivly status` would tell you to key an entity `client-accounts` and then
2148
+ * `defineClivlyConfig` would reject the config it had just asked for. Same bug,
2149
+ * opposite half of the product.
2150
+ *
2151
+ * Widening is the safe direction: every key that was valid before still is, so
2152
+ * nothing existing is renamed. Safe at all because neither use is ever a bare
2153
+ * SQL identifier — the view name is quoted (`view-compiler.ts`, `quoteIdent`),
2154
+ * and so is a join's `key` when it is emitted as an alias.
2155
+ */
2156
+ declare const PROJECTION_KEY_PATTERN: RegExp;
2140
2157
  declare const projectionRefSchema: ZodObject<{
2141
2158
  table: ZodString;
2142
2159
  column: ZodString;
@@ -2638,6 +2655,15 @@ declare function customObjectDeclarations(config: ClivlyEntitiesConfig): CustomO
2638
2655
  * Qualified rather than bare: two bindings can pull the same column name from
2639
2656
  * different tables (`cl.full_name` and `adv.full_name`), which a bare column
2640
2657
  * name cannot tell apart.
2658
+ *
2659
+ * This is the single derivation of that map, and both consumers depend on it
2660
+ * emitting ref tokens. `mappingsToEntitiesConfig` populates a projection
2661
+ * entity's required `fields`; the cloud ingest path needs the same map because
2662
+ * a projection's persisted `crm_entity_mappings.field_map` is empty by design,
2663
+ * so `rowToSourceRow` derives the effective field map from the projection
2664
+ * instead. The tokens emitted here are exactly how `fromDrizzleProjection` keys
2665
+ * the rows it pushes (`drizzle-projection.ts` `refToken`), so the two line up.
2666
+ * Deriving this a second time anywhere would let the copies drift.
2641
2667
  */
2642
2668
  declare function projectionFields(projection: ProjectionMapping): Record<string, string>;
2643
2669
  declare function defineClivlyConfig(config: ClivlyEntitiesConfigInput, options?: {
@@ -2698,4 +2724,4 @@ interface ConfigValidationResult {
2698
2724
  */
2699
2725
  declare function validateEntitiesConfig(config: ClivlyEntitiesConfig, schema: DiscoveredSchemaTable[]): ConfigValidationResult;
2700
2726
  //#endregion
2701
- export { BASE_REF as A, defineClivlyConfig as C, relationshipSchema as D, projectionFields as E, ProjectionRef as F, identityRefs as I, projectionSchema as L, ProjectionIdentity as M, ProjectionJoin as N, relationshipsSchema as O, ProjectionMapping as P, customObjectDeclarations as S, filterSchema as T, MATERIALIZING_CONCEPTS as _, ClivlyEntitiesConfig as a, RelationshipVia as b, ClivlyEntityConfigInput as c, CrmConcept as d, CustomObjectDeclaration as f, FilterValue as g, FilterExpr as h, ClivlyConfigError as i, ProjectionFieldBinding as j, validateEntitiesConfig as k, ConfigValidationError as l, DiscoveredSchemaTable as m, CRM_CONCEPTS as n, ClivlyEntitiesConfigInput as o, DiscoveredColumnMeta as p, CUSTOM_CONCEPT as r, ClivlyEntityConfig as s, CANONICAL_FIELDS as t, ConfigValidationResult as u, MaterializingConcept as v, entitiesConfigSchema as w, RelationshipsMap as x, RelationshipSpec as y };
2727
+ export { BASE_REF as A, defineClivlyConfig as C, relationshipSchema as D, projectionFields as E, ProjectionMapping as F, ProjectionRef as I, identityRefs as L, ProjectionFieldBinding as M, ProjectionIdentity as N, relationshipsSchema as O, ProjectionJoin as P, projectionSchema as R, customObjectDeclarations as S, filterSchema as T, MATERIALIZING_CONCEPTS as _, ClivlyEntitiesConfig as a, RelationshipVia as b, ClivlyEntityConfigInput as c, CrmConcept as d, CustomObjectDeclaration as f, FilterValue as g, FilterExpr as h, ClivlyConfigError as i, PROJECTION_KEY_PATTERN as j, validateEntitiesConfig as k, ConfigValidationError as l, DiscoveredSchemaTable as m, CRM_CONCEPTS as n, ClivlyEntitiesConfigInput as o, DiscoveredColumnMeta as p, CUSTOM_CONCEPT as r, ClivlyEntityConfig as s, CANONICAL_FIELDS as t, ConfigValidationResult as u, MaterializingConcept as v, entitiesConfigSchema as w, RelationshipsMap as x, RelationshipSpec as y };
@@ -1,4 +1,4 @@
1
- const require_projection = require("./projection-DEwL-D8R.cjs");
1
+ const require_projection = require("./projection-Dz6_i1-w.cjs");
2
2
  //#region ../core/src/schema-meta.ts
3
3
  /**
4
4
  * Accessors over a discovered table's schema-v2 metadata.
@@ -29,7 +29,17 @@ function coversUniqueKey(table, columns) {
29
29
  const present = new Set(columns);
30
30
  return (table.uniqueConstraints ?? []).some((key) => key.length > 0 && key.every((column) => present.has(column)));
31
31
  }
32
- /** Is this table's payload rich enough to validate a projection against? */
32
+ /**
33
+ * Is this table's payload rich enough to validate a projection against?
34
+ *
35
+ * A type predicate rather than a plain boolean so the narrowing is visible to
36
+ * the compiler: callers that guard on this can iterate `columnsMeta` directly
37
+ * instead of casting, and a cast that outlives its guard becomes a type error
38
+ * rather than a silent walk over `undefined`.
39
+ *
40
+ * The invariant is unchanged: this reports whether the metadata is *known*,
41
+ * never whether the table has keys. Absent metadata stays UNKNOWN.
42
+ */
33
43
  function hasRichMeta(table) {
34
44
  return Boolean(table.columnsMeta && table.uniqueConstraints);
35
45
  }
@@ -273,6 +283,64 @@ function checkFanOut(errors, projection, resolver, base, basePk) {
273
283
  });
274
284
  }
275
285
  /**
286
+ * One error naming a filter column that is not on the projection's base table,
287
+ * and the base table it would have to be on.
288
+ */
289
+ function columnOnBase(column, baseTable, path) {
290
+ if (baseTable?.columns.includes(column)) return [];
291
+ return [{
292
+ path,
293
+ message: `filter column "${column}" does not exist on "${baseTable?.name ?? "the base table"}" — a derived object is filtered by its base table's columns only, so name one of those, or move the condition into a join's \`on\``
294
+ }];
295
+ }
296
+ /**
297
+ * A derived object's filter is evaluated in the cloud against pushed rows, so
298
+ * it can only name columns of the base table.
299
+ *
300
+ * Every rejected shape here fails *silently* today, which is the whole reason
301
+ * the check exists — a filter that cannot be honoured must error, never quietly
302
+ * return the wrong rows. The three shapes fail in different directions:
303
+ *
304
+ * - `$raw` is opaque SQL meant for the embedded model. The cloud path cannot
305
+ * evaluate it and passes every row, so the filter is ignored and the object
306
+ * syncs UNFILTERED.
307
+ * - A joined-table column resolves to nothing on a pushed row, so under a
308
+ * positive operator (`eq`, `in`) no row matches and the object syncs EMPTY.
309
+ * - The same unresolvable column under a NEGATIVE operator (`ne`, `isNull`,
310
+ * `notIn`) is the dangerous one: the missing cell reads as null, so the
311
+ * predicate is true for every row and a filter meant to narrow the sync
312
+ * ADMITS EVERYTHING. Nobody notices extra rows arriving.
313
+ *
314
+ * Rejecting an unresolvable column closes the last two together.
315
+ */
316
+ function validateProjectionFilter(filter, projection, schema, base) {
317
+ if (!filter) return [];
318
+ const path = `${base}.filter`;
319
+ if ("$raw" in filter) return [{
320
+ path,
321
+ message: "a $raw filter cannot be evaluated against a derived object's rows — it is opaque SQL, so the cloud passes every row through unfiltered. Express it as column conditions on the base table instead"
322
+ }];
323
+ const baseTable = schema.find((t) => t.name === projection.baseTable);
324
+ const errors = [];
325
+ for (const column of Object.keys(filter)) {
326
+ if (column.includes(".")) {
327
+ const [ref, ...rest] = column.split(".");
328
+ const bare = rest.join(".");
329
+ if (ref !== "base") {
330
+ errors.push({
331
+ path,
332
+ message: `filter column "${column}" names the join "${ref}", but a derived object is filtered by its base table ("${projection.baseTable}") only — filter on a base column, or move the condition into that join's \`on\``
333
+ });
334
+ continue;
335
+ }
336
+ errors.push(...columnOnBase(bare, baseTable, path));
337
+ continue;
338
+ }
339
+ errors.push(...columnOnBase(column, baseTable, path));
340
+ }
341
+ return errors;
342
+ }
343
+ /**
276
344
  * Validate a projection against the discovered schema. Returns every error
277
345
  * found (not just the first) so a builder UI can show them all at once.
278
346
  */
@@ -545,6 +613,15 @@ function customObjectDeclarations(config) {
545
613
  * Qualified rather than bare: two bindings can pull the same column name from
546
614
  * different tables (`cl.full_name` and `adv.full_name`), which a bare column
547
615
  * name cannot tell apart.
616
+ *
617
+ * This is the single derivation of that map, and both consumers depend on it
618
+ * emitting ref tokens. `mappingsToEntitiesConfig` populates a projection
619
+ * entity's required `fields`; the cloud ingest path needs the same map because
620
+ * a projection's persisted `crm_entity_mappings.field_map` is empty by design,
621
+ * so `rowToSourceRow` derives the effective field map from the projection
622
+ * instead. The tokens emitted here are exactly how `fromDrizzleProjection` keys
623
+ * the rows it pushes (`drizzle-projection.ts` `refToken`), so the two line up.
624
+ * Deriving this a second time anywhere would let the copies drift.
548
625
  */
549
626
  function projectionFields(projection) {
550
627
  const fields = {};
@@ -683,7 +760,7 @@ function validateEntity(v, config, key, entity, schema) {
683
760
  if (entity.source !== entity.projection.baseTable) v.report(`${base}.source`, `"source" ("${entity.source}") must match "projection.baseTable" ("${entity.projection.baseTable}") — the view compiler reads baseTable and the sync engine reads source, so a mismatch makes them disagree about which table this entity is`);
684
761
  for (const error of validateProjection(entity.projection, schema, base)) v.report(error.path, error.message);
685
762
  validateReadOnly(v, base, entity);
686
- validateFilter(v, base, entity);
763
+ for (const error of validateProjectionFilter(entity.filter, entity.projection, schema, base)) v.report(error.path, error.message);
687
764
  return;
688
765
  }
689
766
  validateFields(v, base, entity);
@@ -829,3 +906,9 @@ Object.defineProperty(exports, "validateProjection", {
829
906
  return validateProjection;
830
907
  }
831
908
  });
909
+ Object.defineProperty(exports, "validateProjectionFilter", {
910
+ enumerable: true,
911
+ get: function() {
912
+ return validateProjectionFilter;
913
+ }
914
+ });
package/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_entity_config = require("./entity-config-CPM-Tp-d.cjs");
3
+ const require_entity_config = require("./entity-config-DgAe0_YD.cjs");
4
4
  require("./mapping-score-CaB3F4Uu.cjs");
5
5
  const require_internal_entity_heuristics = require("./internal-entity-heuristics.cjs");
6
- const require_sdk = require("./sdk-BL9BCzyD.cjs");
6
+ const require_sdk = require("./sdk-B1vX4kUJ.cjs");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_fs = require("node:fs");
9
9
  let node_module = require("node:module");
@@ -306,16 +306,27 @@ function addCommand(pm, packages) {
306
306
  */
307
307
  const CLIVLY_TICK_PATH = "/api/clivly/tick";
308
308
  /**
309
- * What `clivly login` writes into `.env` for `CLIVLY_SYNC_TRIGGER_URL`.
309
+ * The example shape `clivly init`/`clivly login` show for `CLIVLY_SYNC_TRIGGER_URL`
310
+ * — commented out in `.env`, not written as a live value.
310
311
  *
311
- * A visible template beats an empty line: the developer sees the shape they
312
- * eventually need — a public origin plus the tick path — instead of an unhelpful
313
- * `CLIVLY_SYNC_TRIGGER_URL=` they have to go and look up.
312
+ * It used to be scaffolded uncommented, on the theory that a visible template
313
+ * beats an empty line. The SDK's `isPlaceholderUrl` guard
314
+ * (`@clivly/sdk/trigger-url`) already keeps a bracketed placeholder like this
315
+ * one out of `syncTrigger.url` on every heartbeat — `resolveTriggerUrl` skips
316
+ * it in favour of the dev tunnel / platform-derived origin — and
317
+ * `assertSafeTriggerUrl` on the API side rejects it outright if it were ever
318
+ * sent, since `<yourDomain.com>` doesn't even parse as a URL. So it was never
319
+ * actually fireable (F-12 is not an SSRF/overwrite bug).
314
320
  *
315
- * It is safe to ship a truthy non-address here only because the SDK refuses to
316
- * treat a bracketed value as configured (`isPlaceholderUrl` in
317
- * `@clivly/sdk/trigger-url`). Change the brackets here and that guard stops
318
- * matching, so the two must stay in step.
321
+ * What it DID break: readiness reporting only checked *presence*, not
322
+ * validity, so an unedited placeholder made the dashboard's setup ladder and
323
+ * `triggerConfigured` report "done" while `clivly status` correctly flagged
324
+ * it — two surfaces disagreeing about a workspace that could never run "Run
325
+ * sync". See `hasWorkingTrigger` (`@clivly.com/api/sync-trigger`) for the
326
+ * fix. Commenting the example out here removes the confusing truthy-but-fake
327
+ * value at the source, on top of that fix, while keeping the shape visible
328
+ * for the (uncommon) case a developer needs it — most platforms are
329
+ * auto-detected, and `clivly dev` handles local development.
319
330
  */
320
331
  const TRIGGER_URL_PLACEHOLDER = `https://<yourDomain.com>${CLIVLY_TICK_PATH}`;
321
332
  //#endregion
@@ -1440,8 +1451,8 @@ function credentialEntries(credentials) {
1440
1451
  },
1441
1452
  {
1442
1453
  key: "CLIVLY_SYNC_TRIGGER_URL",
1443
- value: TRIGGER_URL_PLACEHOLDER,
1444
- comment: "Public URL of your tick route — replace <yourDomain.com> once deployed. Local dev: run `clivly dev` instead."
1454
+ value: "",
1455
+ comment: `Public URL of your tick route. Only needed when the platform's own URL can't be auto-detected (see \`clivly status\`). Uncomment and set your deployed origin if so:\n# CLIVLY_SYNC_TRIGGER_URL=${TRIGGER_URL_PLACEHOLDER}`
1445
1456
  }
1446
1457
  ];
1447
1458
  }
@@ -1810,8 +1821,7 @@ const CLIVLY_ENV_KEYS = [
1810
1821
  },
1811
1822
  {
1812
1823
  key: "CLIVLY_SYNC_TRIGGER_URL",
1813
- value: TRIGGER_URL_PLACEHOLDER,
1814
- comment: "Public URL of your tick route — replace <yourDomain.com> once deployed. Local dev: run `clivly dev` instead."
1824
+ comment: `Public URL of your tick route. Only needed when the platform's own URL can't be auto-detected (see \`clivly status\`). Uncomment and set your deployed origin if so:\n# CLIVLY_SYNC_TRIGGER_URL=${TRIGGER_URL_PLACEHOLDER}`
1815
1825
  },
1816
1826
  {
1817
1827
  key: "CLIVLY_SYNC_TRIGGER_SECRET",
package/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { t as CANONICAL_FIELDS } from "./entity-config-C8IIkrmX.mjs";
2
+ import { t as CANONICAL_FIELDS } from "./entity-config-C4d3K9CP.mjs";
3
3
  import "./mapping-score-C68uAqGB.mjs";
4
4
  import { matchesConcept, requiredFieldsFor, scoreFieldMap, scoreRelationships, suggestCursorField, toFieldMap } from "./internal-entity-heuristics.mjs";
5
- import { s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-CubnsyD9.mjs";
5
+ import { s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-D6gX0TXS.mjs";
6
6
  import { createRequire } from "node:module";
7
7
  import { spawn } from "node:child_process";
8
8
  import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, watch, writeFileSync } from "node:fs";
@@ -305,16 +305,27 @@ function addCommand(pm, packages) {
305
305
  */
306
306
  const CLIVLY_TICK_PATH = "/api/clivly/tick";
307
307
  /**
308
- * What `clivly login` writes into `.env` for `CLIVLY_SYNC_TRIGGER_URL`.
308
+ * The example shape `clivly init`/`clivly login` show for `CLIVLY_SYNC_TRIGGER_URL`
309
+ * — commented out in `.env`, not written as a live value.
309
310
  *
310
- * A visible template beats an empty line: the developer sees the shape they
311
- * eventually need — a public origin plus the tick path — instead of an unhelpful
312
- * `CLIVLY_SYNC_TRIGGER_URL=` they have to go and look up.
311
+ * It used to be scaffolded uncommented, on the theory that a visible template
312
+ * beats an empty line. The SDK's `isPlaceholderUrl` guard
313
+ * (`@clivly/sdk/trigger-url`) already keeps a bracketed placeholder like this
314
+ * one out of `syncTrigger.url` on every heartbeat — `resolveTriggerUrl` skips
315
+ * it in favour of the dev tunnel / platform-derived origin — and
316
+ * `assertSafeTriggerUrl` on the API side rejects it outright if it were ever
317
+ * sent, since `<yourDomain.com>` doesn't even parse as a URL. So it was never
318
+ * actually fireable (F-12 is not an SSRF/overwrite bug).
313
319
  *
314
- * It is safe to ship a truthy non-address here only because the SDK refuses to
315
- * treat a bracketed value as configured (`isPlaceholderUrl` in
316
- * `@clivly/sdk/trigger-url`). Change the brackets here and that guard stops
317
- * matching, so the two must stay in step.
320
+ * What it DID break: readiness reporting only checked *presence*, not
321
+ * validity, so an unedited placeholder made the dashboard's setup ladder and
322
+ * `triggerConfigured` report "done" while `clivly status` correctly flagged
323
+ * it — two surfaces disagreeing about a workspace that could never run "Run
324
+ * sync". See `hasWorkingTrigger` (`@clivly.com/api/sync-trigger`) for the
325
+ * fix. Commenting the example out here removes the confusing truthy-but-fake
326
+ * value at the source, on top of that fix, while keeping the shape visible
327
+ * for the (uncommon) case a developer needs it — most platforms are
328
+ * auto-detected, and `clivly dev` handles local development.
318
329
  */
319
330
  const TRIGGER_URL_PLACEHOLDER = `https://<yourDomain.com>${CLIVLY_TICK_PATH}`;
320
331
  //#endregion
@@ -1439,8 +1450,8 @@ function credentialEntries(credentials) {
1439
1450
  },
1440
1451
  {
1441
1452
  key: "CLIVLY_SYNC_TRIGGER_URL",
1442
- value: TRIGGER_URL_PLACEHOLDER,
1443
- comment: "Public URL of your tick route — replace <yourDomain.com> once deployed. Local dev: run `clivly dev` instead."
1453
+ value: "",
1454
+ comment: `Public URL of your tick route. Only needed when the platform's own URL can't be auto-detected (see \`clivly status\`). Uncomment and set your deployed origin if so:\n# CLIVLY_SYNC_TRIGGER_URL=${TRIGGER_URL_PLACEHOLDER}`
1444
1455
  }
1445
1456
  ];
1446
1457
  }
@@ -1809,8 +1820,7 @@ const CLIVLY_ENV_KEYS = [
1809
1820
  },
1810
1821
  {
1811
1822
  key: "CLIVLY_SYNC_TRIGGER_URL",
1812
- value: TRIGGER_URL_PLACEHOLDER,
1813
- comment: "Public URL of your tick route — replace <yourDomain.com> once deployed. Local dev: run `clivly dev` instead."
1823
+ comment: `Public URL of your tick route. Only needed when the platform's own URL can't be auto-detected (see \`clivly status\`). Uncomment and set your deployed origin if so:\n# CLIVLY_SYNC_TRIGGER_URL=${TRIGGER_URL_PLACEHOLDER}`
1814
1824
  },
1815
1825
  {
1816
1826
  key: "CLIVLY_SYNC_TRIGGER_SECRET",