clivly 0.4.1 → 0.7.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 (164) hide show
  1. package/README.md +89 -10
  2. package/{adapter-CXr12l4K.d.cts → adapter-C2eRbBfo.d.cts} +1 -2
  3. package/{adapter-CvNBD_3y.d.mts → adapter-D5LRa-NA.d.mts} +1 -2
  4. package/{auth-adapter-CdLODn2p.d.cts → auth-adapter-Bjgo8LhN.d.mts} +1 -2
  5. package/{auth-adapter-CRBW4elj.d.mts → auth-adapter-DWzgl5Yq.d.cts} +1 -2
  6. package/auth-better-auth.d.cts +1 -2
  7. package/auth-better-auth.d.mts +1 -2
  8. package/auth-clerk.d.cts +1 -2
  9. package/auth-clerk.d.mts +1 -2
  10. package/auth-workos.d.cts +1 -2
  11. package/auth-workos.d.mts +1 -2
  12. package/core-adapter.cjs +5 -0
  13. package/core-adapter.d.cts +1 -1
  14. package/core-adapter.d.mts +1 -1
  15. package/core-adapter.mjs +5 -0
  16. package/core-auth-adapter.cjs +6 -1
  17. package/core-auth-adapter.d.cts +1 -1
  18. package/core-auth-adapter.d.mts +1 -1
  19. package/core-auth-adapter.mjs +6 -1
  20. package/core-drizzle.cjs +6 -30
  21. package/core-drizzle.d.cts +1 -20
  22. package/core-drizzle.d.mts +1 -20
  23. package/core-drizzle.mjs +5 -29
  24. package/core-entity-config.cjs +4 -1
  25. package/core-entity-config.d.cts +1 -1
  26. package/core-entity-config.d.mts +1 -1
  27. package/core-entity-config.mjs +5 -1
  28. package/core-entity-heuristics.cjs +14 -453
  29. package/core-entity-heuristics.d.cts +1 -67
  30. package/core-entity-heuristics.d.mts +1 -67
  31. package/core-entity-heuristics.mjs +4 -442
  32. package/core-mapping-form.cjs +6 -1
  33. package/core-mapping-form.d.cts +1 -1
  34. package/core-mapping-form.d.mts +1 -1
  35. package/core-mapping-form.mjs +6 -1
  36. package/core-mapping-score.cjs +4 -1
  37. package/core-mapping-score.d.cts +1 -1
  38. package/core-mapping-score.d.mts +1 -1
  39. package/core-mapping-score.mjs +5 -1
  40. package/core-mappings-config.cjs +6 -1
  41. package/core-mappings-config.d.cts +1 -1
  42. package/core-mappings-config.d.mts +1 -1
  43. package/core-mappings-config.mjs +6 -1
  44. package/core-sync-engine.cjs +6 -1
  45. package/core-sync-engine.d.cts +1 -1
  46. package/core-sync-engine.d.mts +1 -1
  47. package/core-sync-engine.mjs +6 -1
  48. package/core-types.cjs +5 -0
  49. package/core-types.d.cts +1 -1
  50. package/core-types.d.mts +1 -1
  51. package/core-types.mjs +5 -0
  52. package/core-view-compiler.cjs +6 -1
  53. package/core-view-compiler.d.cts +1 -1
  54. package/core-view-compiler.d.mts +1 -1
  55. package/core-view-compiler.mjs +6 -1
  56. package/core.cjs +11 -6
  57. package/core.d.cts +9 -9
  58. package/core.d.mts +9 -9
  59. package/core.mjs +11 -6
  60. package/deprecate-subpath-B9ngvizt.cjs +30 -0
  61. package/deprecate-subpath-CmvZtCpE.mjs +25 -0
  62. package/drizzle-B89tLo4T.mjs +31 -0
  63. package/drizzle-CFOr9dU9.d.mts +20 -0
  64. package/drizzle-DiReZf6d.cjs +36 -0
  65. package/drizzle-DmeKSqeF.d.cts +20 -0
  66. package/drizzle.cjs +3 -1
  67. package/drizzle.d.cts +11 -3
  68. package/drizzle.d.mts +11 -3
  69. package/drizzle.mjs +3 -2
  70. package/{entity-config-DMuZpchz.cjs → entity-config-D87rV-tE.cjs} +194 -192
  71. package/{entity-config-D1DFyeUp.d.mts → entity-config-DoUCXtq5.d.cts} +48 -35
  72. package/{entity-config-GczjD3y8.d.cts → entity-config-DoUCXtq5.d.mts} +48 -35
  73. package/{entity-config-D3YpQB0z.mjs → entity-config-va2bkH5o.mjs} +194 -192
  74. package/index.cjs +910 -234
  75. package/index.d.cts +30 -2
  76. package/index.d.mts +30 -2
  77. package/index.mjs +900 -226
  78. package/internal-adapter.cjs +0 -0
  79. package/internal-adapter.d.cts +2 -0
  80. package/internal-adapter.d.mts +2 -0
  81. package/internal-adapter.mjs +1 -0
  82. package/internal-auth-adapter.cjs +3 -0
  83. package/internal-auth-adapter.d.cts +2 -0
  84. package/internal-auth-adapter.d.mts +2 -0
  85. package/internal-auth-adapter.mjs +2 -0
  86. package/internal-drizzle.cjs +3 -0
  87. package/internal-drizzle.d.cts +2 -0
  88. package/internal-drizzle.d.mts +2 -0
  89. package/internal-drizzle.mjs +2 -0
  90. package/internal-entity-config.cjs +13 -0
  91. package/internal-entity-config.d.cts +2 -0
  92. package/internal-entity-config.d.mts +2 -0
  93. package/internal-entity-config.mjs +2 -0
  94. package/internal-entity-heuristics.cjs +455 -0
  95. package/internal-entity-heuristics.d.cts +67 -0
  96. package/internal-entity-heuristics.d.mts +67 -0
  97. package/internal-entity-heuristics.mjs +444 -0
  98. package/internal-mapping-form.cjs +5 -0
  99. package/internal-mapping-form.d.cts +2 -0
  100. package/internal-mapping-form.d.mts +2 -0
  101. package/internal-mapping-form.mjs +2 -0
  102. package/internal-mapping-score.cjs +12 -0
  103. package/internal-mapping-score.d.cts +2 -0
  104. package/internal-mapping-score.d.mts +2 -0
  105. package/internal-mapping-score.mjs +2 -0
  106. package/internal-mappings-config.cjs +3 -0
  107. package/internal-mappings-config.d.cts +2 -0
  108. package/internal-mappings-config.d.mts +2 -0
  109. package/internal-mappings-config.mjs +2 -0
  110. package/internal-sync-engine.cjs +6 -0
  111. package/internal-sync-engine.d.cts +2 -0
  112. package/internal-sync-engine.d.mts +2 -0
  113. package/internal-sync-engine.mjs +2 -0
  114. package/internal-types.cjs +0 -0
  115. package/internal-types.d.cts +2 -0
  116. package/internal-types.d.mts +2 -0
  117. package/internal-types.mjs +1 -0
  118. package/internal-view-compiler.cjs +6 -0
  119. package/internal-view-compiler.d.cts +2 -0
  120. package/internal-view-compiler.d.mts +2 -0
  121. package/internal-view-compiler.mjs +2 -0
  122. package/internal.cjs +28 -0
  123. package/internal.d.cts +9 -0
  124. package/internal.d.mts +9 -0
  125. package/internal.mjs +7 -0
  126. package/{mapping-form-CUlFVfG8.d.cts → mapping-form-D93lhHEc.d.mts} +1 -2
  127. package/{mapping-form-kjSiJaCX.d.mts → mapping-form-lfyEE7EJ.d.cts} +1 -2
  128. package/{mappings-config-DYZ_uOOB.cjs → mappings-config-BsRADNRv.cjs} +1 -1
  129. package/{mappings-config-CNTZRD4g.mjs → mappings-config-Dbgxx3Nv.mjs} +1 -1
  130. package/{mappings-config-DjRLCb2Q.d.cts → mappings-config-DsUa7-1w.d.mts} +1 -2
  131. package/{mappings-config-GxiYsSBQ.d.mts → mappings-config-XmzB4dag.d.cts} +1 -2
  132. package/package.json +152 -4
  133. package/{sdk-DdzBxgMx.mjs → sdk-C6WANW8M.mjs} +173 -34
  134. package/{sdk-D1BHQDf5.cjs → sdk-iqOXeWIp.cjs} +173 -34
  135. package/sdk.cjs +3 -1
  136. package/sdk.d.cts +1 -41
  137. package/sdk.d.mts +1 -41
  138. package/sdk.mjs +3 -2
  139. package/{sync-engine-DIVMG9EV.d.cts → sync-engine-B7QIbXro.d.cts} +6 -5
  140. package/{sync-engine-BtzkzKmY.cjs → sync-engine-CzEbpQGv.cjs} +1 -1
  141. package/{sync-engine-DLx243iw.d.mts → sync-engine-DGk_AeHJ.d.mts} +6 -5
  142. package/{sync-engine-D9vghQab.mjs → sync-engine-Do5ru3WF.mjs} +1 -1
  143. package/types-CsL0rFYh.d.cts +155 -0
  144. package/types-CsL0rFYh.d.mts +155 -0
  145. package/{view-compiler-4HsRUTWH.d.cts → view-compiler-BWz0sGdm.d.cts} +1 -2
  146. package/{view-compiler-BpIDExjd.d.mts → view-compiler-Bn_EJJMO.d.mts} +1 -2
  147. package/vite.d.cts +1 -2
  148. package/vite.d.mts +1 -2
  149. package/namespace-probe-CA4Vj40g.d.mts +0 -331
  150. package/namespace-probe-CkHCj9tQ.d.cts +0 -331
  151. /package/{auth-adapter-CBOKDhlc.cjs → auth-adapter-CiEXcweZ.cjs} +0 -0
  152. /package/{auth-adapter-wqnnEjo3.mjs → auth-adapter-gfVDxf3d.mjs} +0 -0
  153. /package/{drizzle-meta-BVR1qiyu.cjs → drizzle-meta-Bb1JWYqf.cjs} +0 -0
  154. /package/{drizzle-meta-DpMKataU.mjs → drizzle-meta-D-5k3n0i.mjs} +0 -0
  155. /package/{mapping-form-DdmDebC3.cjs → mapping-form-BpfON_ME.cjs} +0 -0
  156. /package/{mapping-form-BHtPexzI.mjs → mapping-form-DigkuvFc.mjs} +0 -0
  157. /package/{mapping-score-CPPvwoZC.mjs → mapping-score-C68uAqGB.mjs} +0 -0
  158. /package/{mapping-score-i3p8LWDF.cjs → mapping-score-CaB3F4Uu.cjs} +0 -0
  159. /package/{mapping-score-A2lRnlpk.d.mts → mapping-score-D7Gzg7Ii.d.cts} +0 -0
  160. /package/{mapping-score-BcjC0ZQs.d.cts → mapping-score-D7Gzg7Ii.d.mts} +0 -0
  161. /package/{types-BpsKVccL.d.cts → types-B1oRXdNP.d.cts} +0 -0
  162. /package/{types-_qbJXMt_.d.mts → types-B1oRXdNP.d.mts} +0 -0
  163. /package/{view-compiler-BcgJINr7.cjs → view-compiler-BIwvm-o2.cjs} +0 -0
  164. /package/{view-compiler-DeZzx_jn.mjs → view-compiler-ChWr_Lml.mjs} +0 -0
@@ -1,4 +1,83 @@
1
- const require_drizzle_meta = require("./drizzle-meta-BVR1qiyu.cjs");
1
+ const require_drizzle_meta = require("./drizzle-meta-Bb1JWYqf.cjs");
2
+ //#region ../sdk/src/entity-source.ts
3
+ const CONCEPT_KEYS = {
4
+ contact: "contacts",
5
+ company: "companies",
6
+ deal: "deals"
7
+ };
8
+ function resolveEntitySource(config, entityKey) {
9
+ const entity = config.entities?.entities?.[entityKey];
10
+ const src = config.source;
11
+ if (!(entity && src)) return null;
12
+ const conceptKey = CONCEPT_KEYS[entity.concept];
13
+ if (conceptKey) {
14
+ const source = src[conceptKey];
15
+ return source ? {
16
+ concept: entity.concept,
17
+ expectedKey: conceptKey,
18
+ source
19
+ } : null;
20
+ }
21
+ if (entity.concept === "custom") {
22
+ const match = (src.custom ?? []).find((c) => c.entity === entity.source || c.entity === entityKey);
23
+ return match ? {
24
+ concept: "custom",
25
+ expectedKey: "custom",
26
+ source: match
27
+ } : null;
28
+ }
29
+ return null;
30
+ }
31
+ /** The `config.source` key an entity's concept requires, present or not. */
32
+ function expectedSourceKey(concept) {
33
+ return CONCEPT_KEYS[concept] ?? "custom";
34
+ }
35
+ //#endregion
36
+ //#region ../sdk/src/concept-coverage.ts
37
+ const CHECK_NAME = "Entity → source coverage";
38
+ function configuredKeys(config) {
39
+ const src = config.source;
40
+ if (!src) return [];
41
+ return Object.entries(src).filter(([, value]) => Array.isArray(value) ? value.length > 0 : Boolean(value)).map(([key]) => key);
42
+ }
43
+ /**
44
+ * Every configured entity must resolve to a source, or it silently syncs
45
+ * nothing. This is the check that catches `source` keyed by entity name rather
46
+ * than by CRM concept — a mistake that otherwise shows up only as a run that
47
+ * succeeds with zero rows.
48
+ */
49
+ function checkConceptCoverage(config) {
50
+ const entities = config.entities?.entities ?? {};
51
+ const keys = Object.keys(entities);
52
+ if (keys.length === 0) return {
53
+ name: CHECK_NAME,
54
+ ok: false,
55
+ skipped: true,
56
+ detail: "No entities configured — nothing to cross-check."
57
+ };
58
+ const unresolved = keys.filter((key) => resolveEntitySource(config, key) === null);
59
+ if (unresolved.length === 0) return {
60
+ name: CHECK_NAME,
61
+ ok: true,
62
+ detail: `${keys.length} entit${keys.length === 1 ? "y" : "ies"} resolve to a configured source.`
63
+ };
64
+ const available = configuredKeys(config);
65
+ const listed = available.length > 0 ? available.join(", ") : "(none)";
66
+ const problems = unresolved.map((key) => {
67
+ const concept = entities[key]?.concept ?? "unknown";
68
+ if (concept === "custom") {
69
+ const custom = config.source?.custom ?? [];
70
+ if (custom.length > 0) return `"${key}" has concept "custom" but no entry in \`source.custom\` matches it — it will never sync. Entities configured in \`source.custom\`: ${custom.map((c) => c.entity).join(", ")}.`;
71
+ }
72
+ return `"${key}" has concept "${concept}" but no \`source.${expectedSourceKey(concept)}\` is configured — it will never sync.`;
73
+ });
74
+ return {
75
+ name: CHECK_NAME,
76
+ ok: false,
77
+ detail: `${problems.join(" ")} Configured source keys: ${listed}. \`source\` is keyed by CRM concept, not by entity key.`
78
+ };
79
+ }
80
+ //#endregion
2
81
  //#region ../sdk/src/custom-slug.ts
3
82
  const NON_ALNUM = /[^a-z0-9]+/g;
4
83
  const EDGE_HYPHENS = /(^-|-$)/g;
@@ -79,6 +158,56 @@ function describeFetchError(entity, error) {
79
158
  return `Couldn't read "${entity}": ${firstLine(root instanceof Error ? root.message : String(root?.message ?? root ?? "unknown error"))}`;
80
159
  }
81
160
  //#endregion
161
+ //#region ../sdk/src/mapping-dry-run.ts
162
+ /**
163
+ * Fetch one real row and push it through the entity's field map.
164
+ *
165
+ * The distinction from "Sample fetch": that proves the query runs, this proves
166
+ * the row that comes back actually carries the columns `fields` names. A
167
+ * mapping that points at a column the row lacks produces empty CRM records
168
+ * rather than an error, so it has to be caught here.
169
+ *
170
+ * An empty table SKIPS rather than fails — a fresh database is not a
171
+ * misconfiguration, and failing on it would train people to ignore the check.
172
+ */
173
+ async function runMappingDryRun(config, entityKey) {
174
+ const name = `Field mapping dry run (${entityKey})`;
175
+ const resolved = resolveEntitySource(config, entityKey);
176
+ if (!resolved) return {
177
+ name,
178
+ ok: false,
179
+ skipped: true,
180
+ detail: `"${entityKey}" resolves to no source — see the coverage check above.`
181
+ };
182
+ const fields = config.entities?.entities?.[entityKey]?.fields ?? {};
183
+ let rows;
184
+ try {
185
+ rows = await resolved.source.fetchPage({
186
+ since: null,
187
+ limit: 1
188
+ });
189
+ } catch (error) {
190
+ return {
191
+ name,
192
+ ok: false,
193
+ detail: describeFetchError(entityKey, error)
194
+ };
195
+ }
196
+ const row = rows[0];
197
+ if (row === void 0) return {
198
+ name,
199
+ ok: false,
200
+ skipped: true,
201
+ detail: `"${resolved.source.entity}" is empty — nothing to map yet.`
202
+ };
203
+ const missing = Object.entries(fields).filter(([, column]) => !(column in row)).map(([field, column]) => `${field} → ${column}`);
204
+ return {
205
+ name,
206
+ ok: missing.length === 0,
207
+ detail: missing.length === 0 ? `Mapped ${Object.keys(fields).length} field(s) from a real "${resolved.source.entity}" row.` : `Row from "${resolved.source.entity}" is missing mapped column(s): ${missing.join(", ")}.`
208
+ };
209
+ }
210
+ //#endregion
82
211
  //#region ../sdk/src/namespace-probe.ts
83
212
  /**
84
213
  * Verify the tables the integration depends on are genuinely reachable:
@@ -169,6 +298,11 @@ async function namespaceCheck(config) {
169
298
  detail: probeResult.reachable ? "All required source tables are reachable." : probeResult.error?.message ?? "namespace unreachable"
170
299
  };
171
300
  }
301
+ async function mappingDryRunChecks(config) {
302
+ const checks = [];
303
+ for (const entityKey of Object.keys(config.entities?.entities ?? {})) checks.push(await runMappingDryRun(config, entityKey));
304
+ return checks;
305
+ }
172
306
  /**
173
307
  * Run the read-only setup checks and return a structured report. Every check
174
308
  * captures its own error into `detail` rather than throwing, so one failure never
@@ -195,6 +329,7 @@ async function runDoctor(args) {
195
329
  ok: sources.length > 0,
196
330
  detail: sources.length > 0 ? `${sources.length} source(s) to mirror.` : "No `source.contacts` — nothing to sync."
197
331
  });
332
+ checks.push(checkConceptCoverage(config));
198
333
  for (const source of sources) {
199
334
  const meta = source[require_drizzle_meta.DRIZZLE_META];
200
335
  if (!meta) {
@@ -233,6 +368,7 @@ async function runDoctor(args) {
233
368
  detail: describeFetchError(source.entity, error)
234
369
  });
235
370
  }
371
+ checks.push(...await mappingDryRunChecks(config));
236
372
  const result = await connect();
237
373
  checks.push({
238
374
  name: "Heartbeat",
@@ -276,6 +412,7 @@ async function syncSource(args) {
276
412
  const idField = source.idField ?? DEFAULT_ID_FIELD;
277
413
  let cursor = since;
278
414
  let pushed = 0;
415
+ const resumedFrom = since === null ? null : since.time.toISOString();
279
416
  for (;;) {
280
417
  const rows = await source.fetchPage({
281
418
  since: cursor,
@@ -287,6 +424,8 @@ async function syncSource(args) {
287
424
  source: source.entity,
288
425
  rows: capped,
289
426
  mode: "delta",
427
+ fetched: rows.length,
428
+ cursor: resumedFrom,
290
429
  ...objectType ? { objectType } : {}
291
430
  })) return {
292
431
  pushed,
@@ -329,6 +468,8 @@ async function fullSyncSource(args) {
329
468
  source: source.entity,
330
469
  rows: all,
331
470
  mode: "full",
471
+ fetched: all.length,
472
+ cursor: null,
332
473
  ...objectType ? { objectType } : {}
333
474
  });
334
475
  return {
@@ -393,9 +534,7 @@ function observedTriggerUrl(req) {
393
534
  const proto = forwardedProto || requestUrl.protocol.replace(":", "");
394
535
  try {
395
536
  return new URL(`${proto}://${host}${requestUrl.pathname}`).toString();
396
- } catch {
397
- return;
398
- }
537
+ } catch {}
399
538
  }
400
539
  /**
401
540
  * Decide which trigger URL to report, in precedence order:
@@ -454,7 +593,7 @@ function originAllowed(origin, allowedOrigins) {
454
593
  if (!origin) return false;
455
594
  return allowedOrigins.includes(origin);
456
595
  }
457
- function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl = DEFAULT_API_URL$1, fetchImpl = fetch }) {
596
+ function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl = DEFAULT_API_URL$1, fetchImpl = fetch, resolveUser }) {
458
597
  const sessionUrl = `${normalizeApiUrl(apiUrl)}/clivly/widget/session`;
459
598
  return async (request) => {
460
599
  if (request.method !== "POST") return new Response(JSON.stringify({ error: "method_not_allowed" }), {
@@ -473,6 +612,12 @@ function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl = DEFAULT_API
473
612
  return jsonResponse$1({ error: "invalid_json" }, 400);
474
613
  }
475
614
  if (!isChatSessionRequestBody(body)) return jsonResponse$1({ error: "invalid_request" }, 400);
615
+ let hostUser = null;
616
+ if (resolveUser) try {
617
+ hostUser = await resolveUser(request);
618
+ } catch {
619
+ console.warn("[clivly] resolveUser threw; continuing without host identity");
620
+ }
476
621
  try {
477
622
  const upstream = await fetchImpl(sessionUrl, {
478
623
  method: "POST",
@@ -485,7 +630,8 @@ function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl = DEFAULT_API
485
630
  widgetId: body.widgetId,
486
631
  visitor: body.visitor,
487
632
  visitorToken: body.visitorToken,
488
- origin
633
+ origin,
634
+ ...hostUser ? { hostUser } : {}
489
635
  })
490
636
  });
491
637
  const responseBody = await upstream.text();
@@ -675,9 +821,7 @@ function connectReason(status) {
675
821
  async function resolveOrg(response) {
676
822
  try {
677
823
  return (await response.json())?.org;
678
- } catch {
679
- return;
680
- }
824
+ } catch {}
681
825
  }
682
826
  function retryAfterMs(res) {
683
827
  const header = res.headers.get("retry-after");
@@ -700,35 +844,30 @@ function createFetcher(retry) {
700
844
  }
701
845
  if (!(res === null || isRetryableStatus(res.status)) || attempt >= maxAttempts) return res;
702
846
  const backoff = baseDelayMs * 2 ** (attempt - 1) + Math.random() * baseDelayMs;
703
- await sleep(res ? retryAfterMs(res) ?? backoff : backoff);
847
+ const wait = res ? retryAfterMs(res) ?? backoff : backoff;
848
+ await sleep(wait);
704
849
  }
705
850
  };
706
851
  }
852
+ const CONCEPT_PATHS = {
853
+ contacts: SYNC_CONTACTS_PATH,
854
+ companies: SYNC_COMPANIES_PATH,
855
+ deals: SYNC_DEALS_PATH,
856
+ custom: SYNC_CUSTOM_PATH
857
+ };
707
858
  function resolveSampleSource(config, entityKey) {
708
- const entity = config.entities?.entities?.[entityKey];
709
- const src = config.source;
710
- if (!(entity && src)) return null;
711
- if (entity.concept === "contact" && src.contacts) return {
712
- source: src.contacts,
713
- path: SYNC_CONTACTS_PATH
714
- };
715
- if (entity.concept === "company" && src.companies) return {
716
- source: src.companies,
717
- path: SYNC_COMPANIES_PATH
718
- };
719
- if (entity.concept === "deal" && src.deals) return {
720
- source: src.deals,
721
- path: SYNC_DEALS_PATH
859
+ const resolved = resolveEntitySource(config, entityKey);
860
+ if (!resolved) return null;
861
+ const path = CONCEPT_PATHS[resolved.expectedKey];
862
+ if (!path) return null;
863
+ return resolved.expectedKey === "custom" ? {
864
+ source: resolved.source,
865
+ path,
866
+ objectType: resolved.source.objectType
867
+ } : {
868
+ source: resolved.source,
869
+ path
722
870
  };
723
- if (entity.concept === "custom") {
724
- const custom = (src.custom ?? []).find((c) => c.entity === entity.source || c.entity === entityKey);
725
- return custom ? {
726
- source: custom,
727
- path: SYNC_CUSTOM_PATH,
728
- objectType: custom.objectType
729
- } : null;
730
- }
731
- return null;
732
871
  }
733
872
  const PREVIEW_LIMIT = 5;
734
873
  function resolvePreviewSource(config, sourceTable) {
@@ -757,7 +896,7 @@ async function handlePreviewRequest(config, preview) {
757
896
  if (!source) return jsonResponse({ error: `No configured source for table "${preview.sourceTable}"` }, 400);
758
897
  const idField = source.idField ?? "id";
759
898
  const filterColumns = preview.filter ? Object.keys(preview.filter).filter((k) => k !== "$raw") : [];
760
- const needed = new Set([
899
+ const needed = /* @__PURE__ */ new Set([
761
900
  idField,
762
901
  ...Object.values(preview.fieldMap),
763
902
  ...filterColumns
package/sdk.cjs CHANGED
@@ -1,8 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sdk = require("./sdk-D1BHQDf5.cjs");
2
+ const require_entity_config = require("./entity-config-D87rV-tE.cjs");
3
+ const require_sdk = require("./sdk-iqOXeWIp.cjs");
3
4
  exports.ClivlyAuthError = require_sdk.ClivlyAuthError;
4
5
  exports.ClivlyError = require_sdk.ClivlyError;
5
6
  exports.ClivlyNetworkError = require_sdk.ClivlyNetworkError;
6
7
  exports.ClivlyRateLimitError = require_sdk.ClivlyRateLimitError;
7
8
  exports.createChatSessionHandler = require_sdk.createChatSessionHandler;
8
9
  exports.createClivlySDK = require_sdk.createClivlySDK;
10
+ exports.defineClivlyConfig = require_entity_config.defineClivlyConfig;
package/sdk.d.cts CHANGED
@@ -1,41 +1 @@
1
- import { a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore } from "./namespace-probe-CkHCj9tQ.cjs";
2
-
3
- //#region ../sdk/src/chat-session.d.ts
4
- declare function createChatSessionHandler({
5
- allowedOrigins,
6
- apiKey,
7
- apiUrl,
8
- fetchImpl
9
- }: CreateChatSessionHandlerOptions): (request: Request) => Promise<Response>;
10
- //#endregion
11
- //#region ../sdk/src/errors.d.ts
12
- declare class ClivlyError extends Error {
13
- /** HTTP status, or null when the request never completed (network error). */
14
- readonly status: number | null;
15
- readonly reason: ClivlyConnectResult["reason"];
16
- /** Whether a retry could plausibly succeed (network, 429, 5xx). */
17
- readonly retryable: boolean;
18
- constructor(result: ClivlyConnectResult, message?: string);
19
- /**
20
- * Build the error subclass matching a failed result's reason, or `null` when
21
- * the result is `ok`.
22
- */
23
- static from(result: ClivlyConnectResult): ClivlyError | null;
24
- }
25
- /** The API key was rejected (401) or lacks access (403). Not retryable. */
26
- declare class ClivlyAuthError extends ClivlyError {}
27
- /** Rate limited (429). Retryable after backoff / `Retry-After`. */
28
- declare class ClivlyRateLimitError extends ClivlyError {}
29
- /** The request never reached the server (network/DNS/TLS). Retryable. */
30
- declare class ClivlyNetworkError extends ClivlyError {}
31
- //#endregion
32
- //#region ../sdk/src/index.d.ts
33
- /**
34
- * Create the Clivly SDK. The implemented surface — heartbeat/discovery reporting
35
- * and the auth-verify handler — connects a developer's app to a running Clivly
36
- * backend today. Tunnel management and CRM write-back are scaffolded (see the
37
- * clearly-marked TODOs) and land with the Cloudflare infrastructure.
38
- */
39
- declare function createClivlySDK(config: ClivlySDKConfig): ClivlySDK;
40
- //#endregion
41
- export { type ChatSessionRequestBody, type ChatWidgetVisitor, ClivlyAuthError, type ClivlyConnectReason, type ClivlyConnectResult, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, type ClivlySDK, type ClivlySDKConfig, type ClivlyUser, type CreateChatSessionHandlerOptions, type CursorStore, type DiscoveredTable, type DoctorCheck, type DoctorReport, createChatSessionHandler, createClivlySDK };
1
+ export {}
package/sdk.d.mts CHANGED
@@ -1,41 +1 @@
1
- import { a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore } from "./namespace-probe-CA4Vj40g.mjs";
2
-
3
- //#region ../sdk/src/chat-session.d.ts
4
- declare function createChatSessionHandler({
5
- allowedOrigins,
6
- apiKey,
7
- apiUrl,
8
- fetchImpl
9
- }: CreateChatSessionHandlerOptions): (request: Request) => Promise<Response>;
10
- //#endregion
11
- //#region ../sdk/src/errors.d.ts
12
- declare class ClivlyError extends Error {
13
- /** HTTP status, or null when the request never completed (network error). */
14
- readonly status: number | null;
15
- readonly reason: ClivlyConnectResult["reason"];
16
- /** Whether a retry could plausibly succeed (network, 429, 5xx). */
17
- readonly retryable: boolean;
18
- constructor(result: ClivlyConnectResult, message?: string);
19
- /**
20
- * Build the error subclass matching a failed result's reason, or `null` when
21
- * the result is `ok`.
22
- */
23
- static from(result: ClivlyConnectResult): ClivlyError | null;
24
- }
25
- /** The API key was rejected (401) or lacks access (403). Not retryable. */
26
- declare class ClivlyAuthError extends ClivlyError {}
27
- /** Rate limited (429). Retryable after backoff / `Retry-After`. */
28
- declare class ClivlyRateLimitError extends ClivlyError {}
29
- /** The request never reached the server (network/DNS/TLS). Retryable. */
30
- declare class ClivlyNetworkError extends ClivlyError {}
31
- //#endregion
32
- //#region ../sdk/src/index.d.ts
33
- /**
34
- * Create the Clivly SDK. The implemented surface — heartbeat/discovery reporting
35
- * and the auth-verify handler — connects a developer's app to a running Clivly
36
- * backend today. Tunnel management and CRM write-back are scaffolded (see the
37
- * clearly-marked TODOs) and land with the Cloudflare infrastructure.
38
- */
39
- declare function createClivlySDK(config: ClivlySDKConfig): ClivlySDK;
40
- //#endregion
41
- export { type ChatSessionRequestBody, type ChatWidgetVisitor, ClivlyAuthError, type ClivlyConnectReason, type ClivlyConnectResult, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, type ClivlySDK, type ClivlySDKConfig, type ClivlyUser, type CreateChatSessionHandlerOptions, type CursorStore, type DiscoveredTable, type DoctorCheck, type DoctorReport, createChatSessionHandler, createClivlySDK };
1
+ export {}
package/sdk.mjs CHANGED
@@ -1,2 +1,3 @@
1
- import { a as ClivlyRateLimitError, i as ClivlyNetworkError, n as ClivlyAuthError, o as createChatSessionHandler, r as ClivlyError, t as createClivlySDK } from "./sdk-DdzBxgMx.mjs";
2
- export { ClivlyAuthError, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, createChatSessionHandler, createClivlySDK };
1
+ import { o as defineClivlyConfig } from "./entity-config-va2bkH5o.mjs";
2
+ import { a as ClivlyRateLimitError, i as ClivlyNetworkError, n as ClivlyAuthError, o as createChatSessionHandler, r as ClivlyError, t as createClivlySDK } from "./sdk-C6WANW8M.mjs";
3
+ export { ClivlyAuthError, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, createChatSessionHandler, createClivlySDK, defineClivlyConfig };
@@ -1,6 +1,5 @@
1
- import { a as ClivlyEntitiesConfig } from "./entity-config-GczjD3y8.cjs";
2
- import { n as CompiledView, t as CompileOptions } from "./view-compiler-4HsRUTWH.cjs";
3
-
1
+ import { a as ClivlyEntitiesConfig } from "./entity-config-DoUCXtq5.cjs";
2
+ import { n as CompiledView, t as CompileOptions } from "./view-compiler-BWz0sGdm.cjs";
4
3
  //#region ../core/src/sync-engine.d.ts
5
4
  /**
6
5
  * Sync engine (Phase 3) — materializes the read-only entity views into Clivly's
@@ -108,13 +107,15 @@ interface SyncStore {
108
107
  /** Current mirror rows for a concept + role (including archived). */
109
108
  listMirror(input: {
110
109
  concept: string;
111
- role: string; /** Target custom-object type id, for `concept: "custom"` entities. */
110
+ role: string;
111
+ /** Target custom-object type id, for `concept: "custom"` entities. */
112
112
  objectTypeId?: string;
113
113
  }): Promise<MirrorRecord[]>;
114
114
  /** Apply the resolved actions to the mirror table for this concept + role. */
115
115
  persist(input: {
116
116
  concept: string;
117
- role: string; /** Target custom-object type id, for `concept: "custom"` entities. */
117
+ role: string;
118
+ /** Target custom-object type id, for `concept: "custom"` entities. */
118
119
  objectTypeId?: string;
119
120
  actions: PersistAction[];
120
121
  }): Promise<void>;
@@ -1,4 +1,4 @@
1
- const require_view_compiler = require("./view-compiler-BcgJINr7.cjs");
1
+ const require_view_compiler = require("./view-compiler-BIwvm-o2.cjs");
2
2
  //#region ../core/src/sync-engine.ts
3
3
  /**
4
4
  * Diff a view's rows against the current mirror population for one entity.
@@ -1,6 +1,5 @@
1
- import { a as ClivlyEntitiesConfig } from "./entity-config-D1DFyeUp.mjs";
2
- import { n as CompiledView, t as CompileOptions } from "./view-compiler-BpIDExjd.mjs";
3
-
1
+ import { a as ClivlyEntitiesConfig } from "./entity-config-DoUCXtq5.mjs";
2
+ import { n as CompiledView, t as CompileOptions } from "./view-compiler-Bn_EJJMO.mjs";
4
3
  //#region ../core/src/sync-engine.d.ts
5
4
  /**
6
5
  * Sync engine (Phase 3) — materializes the read-only entity views into Clivly's
@@ -108,13 +107,15 @@ interface SyncStore {
108
107
  /** Current mirror rows for a concept + role (including archived). */
109
108
  listMirror(input: {
110
109
  concept: string;
111
- role: string; /** Target custom-object type id, for `concept: "custom"` entities. */
110
+ role: string;
111
+ /** Target custom-object type id, for `concept: "custom"` entities. */
112
112
  objectTypeId?: string;
113
113
  }): Promise<MirrorRecord[]>;
114
114
  /** Apply the resolved actions to the mirror table for this concept + role. */
115
115
  persist(input: {
116
116
  concept: string;
117
- role: string; /** Target custom-object type id, for `concept: "custom"` entities. */
117
+ role: string;
118
+ /** Target custom-object type id, for `concept: "custom"` entities. */
118
119
  objectTypeId?: string;
119
120
  actions: PersistAction[];
120
121
  }): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { r as compileEntityViews } from "./view-compiler-DeZzx_jn.mjs";
1
+ import { r as compileEntityViews } from "./view-compiler-ChWr_Lml.mjs";
2
2
  //#region ../core/src/sync-engine.ts
3
3
  /**
4
4
  * Diff a view's rows against the current mirror population for one entity.
@@ -0,0 +1,155 @@
1
+ //#region ../sdk/src/drizzle-meta.d.ts
2
+ declare const DRIZZLE_META: "__clivly_drizzle_source_meta__";
3
+ interface DrizzleSourceMeta {
4
+ /** Drizzle property keys of every column on the source table. */
5
+ columnKeys: string[];
6
+ /** Schema-v2 typed column metadata derived from the Drizzle table. */
7
+ columnsMeta?: DiscoveredColumn[];
8
+ cursorField: string;
9
+ idField: string;
10
+ /** The backing table's real name, so discovery can key metadata by table. */
11
+ tableName?: string;
12
+ }
13
+ //#endregion
14
+ //#region ../sdk/src/types.d.ts
15
+ interface DiscoveredColumn {
16
+ isForeignKey?: boolean;
17
+ isPrimaryKey?: boolean;
18
+ name: string;
19
+ nullable?: boolean;
20
+ /** The column this FK points at, when it is a foreign key. */
21
+ references?: {
22
+ table: string;
23
+ column: string;
24
+ };
25
+ /** SQL/driver type, e.g. "text", "integer", "timestamp". */
26
+ type?: string;
27
+ }
28
+ interface ClivlyUser {
29
+ email: string;
30
+ id: string;
31
+ name?: string;
32
+ }
33
+ interface DoctorCheck {
34
+ /** Human-readable outcome — what passed, or why it failed. */
35
+ detail: string;
36
+ /** Short label, e.g. "API key present". */
37
+ name: string;
38
+ /** True when the check passed. Ignored when `skipped` is true. */
39
+ ok: boolean;
40
+ /** True when the check could not run (e.g. a hand-written source). */
41
+ skipped?: boolean;
42
+ }
43
+ interface DoctorReport {
44
+ checks: DoctorCheck[];
45
+ /** AND of every non-skipped check's `ok`. */
46
+ ok: boolean;
47
+ }
48
+ type ClivlyConnectReason = "ok" | "invalid_api_key" | "forbidden" | "rate_limited" | "server_error" | "client_error" | "network_error";
49
+ interface ClivlyConnectResult {
50
+ ok: boolean;
51
+ /** Org resolved from a successful heartbeat, when the server returns it. */
52
+ org?: {
53
+ id?: string;
54
+ name?: string;
55
+ };
56
+ reason: ClivlyConnectReason;
57
+ /** Whether a retry could plausibly succeed (network, 429, 5xx). */
58
+ retryable: boolean;
59
+ /** HTTP status, or null when the request never completed (network error). */
60
+ status: number | null;
61
+ }
62
+ interface SyncCursor {
63
+ /** The row's id, breaking ties when timestamps collide. */
64
+ id: string;
65
+ /** The row's cursor-field value (typically `updated_at`). */
66
+ time: Date;
67
+ }
68
+ interface SyncSource {
69
+ cursorField: string;
70
+ entity: string;
71
+ fetchPage(args: {
72
+ since: SyncCursor | null;
73
+ limit: number;
74
+ }): Promise<Record<string, unknown>[]>;
75
+ /** Stable, unique tie-breaker column for keyset paging. Defaults to "id". */
76
+ idField?: string;
77
+ /** Set by `fromDrizzle`; lets `doctor` introspect the backing table. */
78
+ [DRIZZLE_META]?: DrizzleSourceMeta;
79
+ }
80
+ type SyncMode = "delta" | "full";
81
+ interface ClivlySDK {
82
+ /**
83
+ * Send a single heartbeat and report the outcome richly: `ok`, the HTTP
84
+ * `status` (null on network failure), a coarse `reason`, whether it's
85
+ * `retryable`, and the resolved `org` when the server returns it. Never throws
86
+ * — inspect the result, or turn a failure into an exception with
87
+ * `ClivlyError.from(result)`. Prefer this over `heartbeat()` for onboarding,
88
+ * health checks, and one-shot serverless calls.
89
+ */
90
+ connect(): Promise<ClivlyConnectResult>;
91
+ /**
92
+ * Build the `/clivly/auth/verify` handler the developer mounts in their app.
93
+ * Clivly's cloud calls it to validate a CRM user's identity against the host
94
+ * app's session. Pass a resolver that reads the host session from the request.
95
+ *
96
+ * The handler authenticates the caller against `config.verifyToken` (presented
97
+ * as `Authorization: Bearer <token>`) before invoking the resolver, so the
98
+ * endpoint can't be used by third parties to probe host sessions. It fails
99
+ * closed with HTTP 503 if `verifyToken` is unset (a missing config value, not a
100
+ * server fault — so it won't trip the host's 5xx error alerting).
101
+ */
102
+ createAuthVerifyHandler(resolveUser: (req: Request) => Promise<ClivlyUser | null> | ClivlyUser | null): (req: Request) => Promise<Response>;
103
+ /**
104
+ * A fetch handler to mount at a route. Each request runs one `runScheduled()`
105
+ * and returns JSON: 200 when the sync succeeded, 500 otherwise.
106
+ *
107
+ * Two callers use it: Clivly Cloud (when someone clicks "Run sync" — those
108
+ * requests are HMAC-signed and carry a run id), and the host's own scheduler
109
+ * (Workers cron, an uptime ping — no body).
110
+ *
111
+ * When `syncTrigger.secret` is set the handler rejects unsigned or
112
+ * badly-signed requests with 401. When it isn't, the route stays open to
113
+ * anyone who can reach it — set a secret if it's publicly routable.
114
+ */
115
+ createClivlyHandler(): (req: Request) => Promise<Response>;
116
+ /** Validate this setup end-to-end (read-only) before the first sync. */
117
+ doctor(): Promise<DoctorReport>;
118
+ /**
119
+ * Send a single heartbeat now. Returns true on success. Kept for the keep-alive
120
+ * loop and back-compat; use `connect()` when you need the failure reason.
121
+ */
122
+ heartbeat(): Promise<boolean>;
123
+ /**
124
+ * One-shot heartbeat + a single sync, with no keep-alive timers. The correct
125
+ * entry point on serverless/edge (Workers, Vercel, Lambda), where `start()`'s
126
+ * `setInterval` loop can't persist between invocations — call this from a
127
+ * scheduled/cron trigger. Throws if a sync push fails (so the run is marked
128
+ * failed); heartbeat is best-effort.
129
+ */
130
+ runScheduled(options?: {
131
+ mode?: SyncMode;
132
+ runId?: string;
133
+ }): Promise<void>;
134
+ /**
135
+ * Send the initial heartbeat and start the keep-alive loop. For long-lived
136
+ * hosts only — on serverless/edge the interval can't persist, so prefer
137
+ * `runScheduled()` / `createClivlyHandler()`. Emits a warning if a serverless
138
+ * runtime is detected.
139
+ */
140
+ start(): Promise<void>;
141
+ /** Stop the keep-alive loop. */
142
+ stop(): void;
143
+ /**
144
+ * Sync all configured sources now. `delta` (default) pages changed rows since
145
+ * the last cursor; `full` pushes each complete entity so the server can
146
+ * reconcile leavers (archive-on-leave). Throws if a push fails, so callers see
147
+ * the failure — the background keep-alive loop swallows and retries instead.
148
+ */
149
+ sync(options?: {
150
+ mode?: SyncMode;
151
+ runId?: string;
152
+ }): Promise<void>;
153
+ }
154
+ //#endregion
155
+ export { SyncSource as n, ClivlySDK as t };