@clivly/sdk 0.9.5 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -330,6 +330,12 @@ import { drizzleIntrospector } from "clivly/drizzle";
330
330
  createClivlySDK({ /* … */, introspector: drizzleIntrospector(db) });
331
331
  ```
332
332
 
333
+ Setting `introspector` also lets Clivly's dashboard **preview a derived object**
334
+ before you save it. Clivly never connects to your database, so your app runs
335
+ that query — read-only, capped at 50 rows, through the sync-trigger route you
336
+ already expose. Without it the dashboard says preview is unavailable and names
337
+ this setting rather than failing.
338
+
333
339
  ## Common errors and troubleshooting
334
340
 
335
341
  | Symptom | Cause | Fix |
@@ -468,6 +474,7 @@ is honoured. Other `4xx` responses return immediately.
468
474
  | `createClivlyHandler()` | `(Request) => Promise<Response>` | Mount at a route |
469
475
  | `createAuthVerifyHandler(resolveUser)` | `(Request) => Promise<Response>` | `503` unless `verifyToken` is set |
470
476
  | `doctor()` | `Promise<DoctorReport>` | `{ ok, checks }` |
477
+ | `describeSyncTrigger()` | `SyncTriggerDescription` | `{ configured, resolved }` — the raw `syncTrigger.url` and the URL that would actually be reported. Computed per call: `resolved` can be `undefined` until the mounted route has served its first request |
471
478
  | `ensureSchema()` | `Promise<EnsureSchemaResult \| null>` | Declares your custom object types **and** the mappings that make them receive rows. `null` = could not deliver; **rejects** if Clivly refuses the config |
472
479
  | `ensureCustomObjects()` | `Promise<{ slug, id, created }[] \| null>` | Object types only, no mappings. Superseded by `ensureSchema()` |
473
480
 
@@ -1,4 +1,4 @@
1
- import { c as CreateChatSessionHandlerOptions } from "./types-Dz1LQYvQ.cjs";
1
+ import { c as CreateChatSessionHandlerOptions } from "./types-CiFEalbP.cjs";
2
2
  //#region src/chat-session.d.ts
3
3
  declare function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl, fetchImpl, resolveUser }: CreateChatSessionHandlerOptions): (request: Request) => Promise<Response>;
4
4
  //#endregion
@@ -1,4 +1,4 @@
1
- import { c as CreateChatSessionHandlerOptions } from "./types-Dz1LQYvQ.mjs";
1
+ import { c as CreateChatSessionHandlerOptions } from "./types-CiFEalbP.mjs";
2
2
  //#region src/chat-session.d.ts
3
3
  declare function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl, fetchImpl, resolveUser }: CreateChatSessionHandlerOptions): (request: Request) => Promise<Response>;
4
4
  //#endregion
package/dist/drizzle.cjs CHANGED
@@ -3,6 +3,20 @@ const require_drizzle_meta = require("./drizzle-meta-CdCs30q3.cjs");
3
3
  let _clivly_core_drizzle = require("@clivly/core/drizzle");
4
4
  let drizzle_orm = require("drizzle-orm");
5
5
  let _clivly_core = require("@clivly/core");
6
+ //#region src/projection-preview.ts
7
+ /** `db.execute` returns either an array or `{ rows }`, depending on driver. */
8
+ function rowsOf(result) {
9
+ if (Array.isArray(result)) return result;
10
+ const rows = result.rows;
11
+ if (Array.isArray(rows)) return rows;
12
+ throw new Error("previewProjection: db.execute() returned neither an array of rows nor { rows }.");
13
+ }
14
+ async function previewProjection(db, projection, opts) {
15
+ const client = db;
16
+ const { sql } = await import("drizzle-orm");
17
+ return rowsOf(await client.execute(sql.raw((0, _clivly_core.compileProjectionPreview)(projection, opts.limit))));
18
+ }
19
+ //#endregion
6
20
  //#region src/drizzle-projection.ts
7
21
  /** `<ref>.<column>` — the token `fields` carries and cloud-ingest reads. */
8
22
  function refToken(ref) {
@@ -134,6 +148,9 @@ function drizzleIntrospector(db) {
134
148
  },
135
149
  async sampleSelect(table) {
136
150
  await client.execute(drizzle_orm.sql`select 1 from ${drizzle_orm.sql.identifier(table)} limit 1`);
151
+ },
152
+ runProjection(projection, opts) {
153
+ return previewProjection(db, projection, opts);
137
154
  }
138
155
  };
139
156
  }
@@ -1,4 +1,4 @@
1
- import { _ as NamespaceIntrospector, g as SyncSource } from "./types-Dz1LQYvQ.cjs";
1
+ import { g as SyncSource, v as NamespaceIntrospector } from "./types-CiFEalbP.cjs";
2
2
  import { ProjectionMapping, ProjectionRef } from "@clivly/core";
3
3
  import { Table } from "drizzle-orm";
4
4
  import { buildUniqueConstraints, discoverFromDrizzle } from "@clivly/core/drizzle";
@@ -1,4 +1,4 @@
1
- import { _ as NamespaceIntrospector, g as SyncSource } from "./types-Dz1LQYvQ.mjs";
1
+ import { g as SyncSource, v as NamespaceIntrospector } from "./types-CiFEalbP.mjs";
2
2
  import { buildUniqueConstraints, discoverFromDrizzle } from "@clivly/core/drizzle";
3
3
  import { Table } from "drizzle-orm";
4
4
  import { ProjectionMapping, ProjectionRef } from "@clivly/core";
package/dist/drizzle.mjs CHANGED
@@ -1,7 +1,21 @@
1
1
  import { t as DRIZZLE_META } from "./drizzle-meta-DNqzkREf.mjs";
2
2
  import { buildColumnsMeta, buildUniqueConstraints, buildUniqueConstraints as buildUniqueConstraints$1, discoverFromDrizzle } from "@clivly/core/drizzle";
3
3
  import { asc, getTableColumns, getTableName, sql } from "drizzle-orm";
4
- import { BASE_REF, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, quoteIdent } from "@clivly/core";
4
+ import { BASE_REF, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, compileProjectionPreview, quoteIdent } from "@clivly/core";
5
+ //#region src/projection-preview.ts
6
+ /** `db.execute` returns either an array or `{ rows }`, depending on driver. */
7
+ function rowsOf(result) {
8
+ if (Array.isArray(result)) return result;
9
+ const rows = result.rows;
10
+ if (Array.isArray(rows)) return rows;
11
+ throw new Error("previewProjection: db.execute() returned neither an array of rows nor { rows }.");
12
+ }
13
+ async function previewProjection(db, projection, opts) {
14
+ const client = db;
15
+ const { sql } = await import("drizzle-orm");
16
+ return rowsOf(await client.execute(sql.raw(compileProjectionPreview(projection, opts.limit))));
17
+ }
18
+ //#endregion
5
19
  //#region src/drizzle-projection.ts
6
20
  /** `<ref>.<column>` — the token `fields` carries and cloud-ingest reads. */
7
21
  function refToken(ref) {
@@ -133,6 +147,9 @@ function drizzleIntrospector(db) {
133
147
  },
134
148
  async sampleSelect(table) {
135
149
  await client.execute(sql`select 1 from ${sql.identifier(table)} limit 1`);
150
+ },
151
+ runProjection(projection, opts) {
152
+ return previewProjection(db, projection, opts);
136
153
  }
137
154
  };
138
155
  }
package/dist/index.cjs CHANGED
@@ -574,12 +574,21 @@ async function runDoctor(args) {
574
574
  ok: entityCount > 0,
575
575
  detail: entityCount > 0 ? `${entityCount} entit${entityCount === 1 ? "y" : "ies"} configured.` : "No `entities` config — pass defineClivlyConfig(...) as `entities`."
576
576
  });
577
+ const sourcesDetail = (count, deferred) => {
578
+ if (deferred) return "Deferred — the coverage check below names the entity and the source key it expected.";
579
+ return count === 0 ? "No `source.contacts` — nothing to sync." : `${count} source(s) to mirror.`;
580
+ };
581
+ const coverage = checkConceptCoverage(config);
582
+ const coverageOwnsIt = !(coverage.ok || coverage.skipped);
583
+ const noSources = sources.length === 0;
584
+ const deferred = noSources && coverageOwnsIt;
577
585
  checks.push({
578
586
  name: "Sources configured",
579
- ok: sources.length > 0,
580
- detail: sources.length > 0 ? `${sources.length} source(s) to mirror.` : "No `source.contacts` — nothing to sync."
587
+ ok: !noSources,
588
+ skipped: deferred,
589
+ detail: sourcesDetail(sources.length, deferred)
581
590
  });
582
- checks.push(checkConceptCoverage(config));
591
+ checks.push(coverage);
583
592
  checks.push(...cursorColumnChecks(sources));
584
593
  for (const source of sources) {
585
594
  const check = cursorMonotonicityCheck(source);
@@ -1446,6 +1455,15 @@ async function handlePreviewRequest(config, preview) {
1446
1455
  sampledCount: rows.length
1447
1456
  }, 200);
1448
1457
  }
1458
+ async function handleProjectionPreviewRequest(config, request) {
1459
+ const run = config.introspector?.runProjection;
1460
+ if (!run) return jsonResponse({ error: "This app cannot preview derived objects. Set `introspector: drizzleIntrospector(db)` in clivly.config.ts, on a clivly version that supports runProjection." }, 400);
1461
+ const rows = await run.call(config.introspector, request.projection, { limit: request.limit });
1462
+ return jsonResponse({
1463
+ rows,
1464
+ sampledCount: rows.length
1465
+ }, 200);
1466
+ }
1449
1467
  /**
1450
1468
  * Create the Clivly SDK. The implemented surface — heartbeat/discovery reporting
1451
1469
  * and the auth-verify handler — connects a developer's app to a running Clivly
@@ -1575,13 +1593,17 @@ function createClivlySDK(config) {
1575
1593
  lastPushFailure = null;
1576
1594
  if (!await syncOne(resolved.source, resolved.path, "delta", resolved.objectType, runId, SAMPLE_BATCH_SIZE, SAMPLE_BATCH_SIZE, false)) throw new Error(describePushFailure([resolved.source.entity]));
1577
1595
  };
1578
- const sendHeartbeat = () => {
1579
- const triggerUrl = resolveTriggerUrl({
1596
+ const describeSyncTrigger = () => ({
1597
+ configured: config.syncTrigger?.url,
1598
+ resolved: resolveTriggerUrl({
1580
1599
  configuredUrl: config.syncTrigger?.url,
1581
1600
  configuredPath: config.syncTrigger?.path,
1582
1601
  env: config.env ?? process.env,
1583
1602
  observedUrl
1584
- });
1603
+ })
1604
+ });
1605
+ const sendHeartbeat = () => {
1606
+ const triggerUrl = describeSyncTrigger().resolved;
1585
1607
  return fetcher(`${apiUrl}/clivly/sdk/heartbeat`, {
1586
1608
  method: "POST",
1587
1609
  headers: {
@@ -1809,6 +1831,7 @@ function createClivlySDK(config) {
1809
1831
  const startedAt = Date.now();
1810
1832
  try {
1811
1833
  if (payload.preview) return await handlePreviewRequest(config, payload.preview);
1834
+ if (payload.previewProjection) return await handleProjectionPreviewRequest(config, payload.previewProjection);
1812
1835
  if (payload.sample) await runSampleSync(payload.sample.entityKey, payload.runId);
1813
1836
  else await runScheduled({
1814
1837
  mode: payload.mode,
@@ -1904,6 +1927,7 @@ function createClivlySDK(config) {
1904
1927
  connect,
1905
1928
  ping,
1906
1929
  doctor,
1930
+ describeSyncTrigger,
1907
1931
  runScheduled,
1908
1932
  createClivlyHandler,
1909
1933
  sync: runSync,
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as ClivlySDK, c as CreateChatSessionHandlerOptions, d as DoctorCheck, f as DoctorReport, h as EnsuredObjectTypeResult, i as ClivlyConnectResult, l as CursorStore, m as EnsuredMappingResult, n as ChatWidgetVisitor, o as ClivlySDKConfig, p as EnsureSchemaResult, r as ClivlyConnectReason, s as ClivlyUser, t as ChatSessionRequestBody, u as DiscoveredTable } from "./types-Dz1LQYvQ.cjs";
1
+ import { _ as SyncTriggerDescription, a as ClivlySDK, c as CreateChatSessionHandlerOptions, d as DoctorCheck, f as DoctorReport, h as EnsuredObjectTypeResult, i as ClivlyConnectResult, l as CursorStore, m as EnsuredMappingResult, n as ChatWidgetVisitor, o as ClivlySDKConfig, p as EnsureSchemaResult, r as ClivlyConnectReason, s as ClivlyUser, t as ChatSessionRequestBody, u as DiscoveredTable } from "./types-CiFEalbP.cjs";
2
2
  import { createChatSessionHandler } from "./chat-session.cjs";
3
3
  import { ClivlyEntitiesConfig, ConfigValidationError, ConfigValidationResult, CustomObjectDeclaration, DiscoveredSchemaTable, customObjectDeclarations, defineClivlyConfig, validateEntitiesConfig } from "@clivly/core/entity-config";
4
4
  //#region src/trigger-url.d.ts
@@ -34,4 +34,4 @@ declare class ClivlyNetworkError extends ClivlyError {}
34
34
  */
35
35
  declare function createClivlySDK(config: ClivlySDKConfig): ClivlySDK;
36
36
  //#endregion
37
- export { type ChatSessionRequestBody, type ChatWidgetVisitor, ClivlyAuthError, type ClivlyConnectReason, type ClivlyConnectResult, type ClivlyEntitiesConfig, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, type ClivlySDK, type ClivlySDKConfig, type ClivlyUser, type ConfigValidationError, type ConfigValidationResult, type CreateChatSessionHandlerOptions, type CursorStore, type CustomObjectDeclaration, type DiscoveredSchemaTable, type DiscoveredTable, type DoctorCheck, type DoctorReport, type EnsureSchemaResult, type EnsuredMappingResult, type EnsuredObjectTypeResult, createChatSessionHandler, createClivlySDK, customObjectDeclarations, defineClivlyConfig, isPlaceholderUrl, validateEntitiesConfig };
37
+ export { type ChatSessionRequestBody, type ChatWidgetVisitor, ClivlyAuthError, type ClivlyConnectReason, type ClivlyConnectResult, type ClivlyEntitiesConfig, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, type ClivlySDK, type ClivlySDKConfig, type ClivlyUser, type ConfigValidationError, type ConfigValidationResult, type CreateChatSessionHandlerOptions, type CursorStore, type CustomObjectDeclaration, type DiscoveredSchemaTable, type DiscoveredTable, type DoctorCheck, type DoctorReport, type EnsureSchemaResult, type EnsuredMappingResult, type EnsuredObjectTypeResult, type SyncTriggerDescription, createChatSessionHandler, createClivlySDK, customObjectDeclarations, defineClivlyConfig, isPlaceholderUrl, validateEntitiesConfig };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as ClivlySDK, c as CreateChatSessionHandlerOptions, d as DoctorCheck, f as DoctorReport, h as EnsuredObjectTypeResult, i as ClivlyConnectResult, l as CursorStore, m as EnsuredMappingResult, n as ChatWidgetVisitor, o as ClivlySDKConfig, p as EnsureSchemaResult, r as ClivlyConnectReason, s as ClivlyUser, t as ChatSessionRequestBody, u as DiscoveredTable } from "./types-Dz1LQYvQ.mjs";
1
+ import { _ as SyncTriggerDescription, a as ClivlySDK, c as CreateChatSessionHandlerOptions, d as DoctorCheck, f as DoctorReport, h as EnsuredObjectTypeResult, i as ClivlyConnectResult, l as CursorStore, m as EnsuredMappingResult, n as ChatWidgetVisitor, o as ClivlySDKConfig, p as EnsureSchemaResult, r as ClivlyConnectReason, s as ClivlyUser, t as ChatSessionRequestBody, u as DiscoveredTable } from "./types-CiFEalbP.mjs";
2
2
  import { createChatSessionHandler } from "./chat-session.mjs";
3
3
  import { ClivlyEntitiesConfig, ConfigValidationError, ConfigValidationResult, CustomObjectDeclaration, DiscoveredSchemaTable, customObjectDeclarations, defineClivlyConfig, validateEntitiesConfig } from "@clivly/core/entity-config";
4
4
  //#region src/trigger-url.d.ts
@@ -34,4 +34,4 @@ declare class ClivlyNetworkError extends ClivlyError {}
34
34
  */
35
35
  declare function createClivlySDK(config: ClivlySDKConfig): ClivlySDK;
36
36
  //#endregion
37
- export { type ChatSessionRequestBody, type ChatWidgetVisitor, ClivlyAuthError, type ClivlyConnectReason, type ClivlyConnectResult, type ClivlyEntitiesConfig, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, type ClivlySDK, type ClivlySDKConfig, type ClivlyUser, type ConfigValidationError, type ConfigValidationResult, type CreateChatSessionHandlerOptions, type CursorStore, type CustomObjectDeclaration, type DiscoveredSchemaTable, type DiscoveredTable, type DoctorCheck, type DoctorReport, type EnsureSchemaResult, type EnsuredMappingResult, type EnsuredObjectTypeResult, createChatSessionHandler, createClivlySDK, customObjectDeclarations, defineClivlyConfig, isPlaceholderUrl, validateEntitiesConfig };
37
+ export { type ChatSessionRequestBody, type ChatWidgetVisitor, ClivlyAuthError, type ClivlyConnectReason, type ClivlyConnectResult, type ClivlyEntitiesConfig, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, type ClivlySDK, type ClivlySDKConfig, type ClivlyUser, type ConfigValidationError, type ConfigValidationResult, type CreateChatSessionHandlerOptions, type CursorStore, type CustomObjectDeclaration, type DiscoveredSchemaTable, type DiscoveredTable, type DoctorCheck, type DoctorReport, type EnsureSchemaResult, type EnsuredMappingResult, type EnsuredObjectTypeResult, type SyncTriggerDescription, createChatSessionHandler, createClivlySDK, customObjectDeclarations, defineClivlyConfig, isPlaceholderUrl, validateEntitiesConfig };
package/dist/index.mjs CHANGED
@@ -573,12 +573,21 @@ async function runDoctor(args) {
573
573
  ok: entityCount > 0,
574
574
  detail: entityCount > 0 ? `${entityCount} entit${entityCount === 1 ? "y" : "ies"} configured.` : "No `entities` config — pass defineClivlyConfig(...) as `entities`."
575
575
  });
576
+ const sourcesDetail = (count, deferred) => {
577
+ if (deferred) return "Deferred — the coverage check below names the entity and the source key it expected.";
578
+ return count === 0 ? "No `source.contacts` — nothing to sync." : `${count} source(s) to mirror.`;
579
+ };
580
+ const coverage = checkConceptCoverage(config);
581
+ const coverageOwnsIt = !(coverage.ok || coverage.skipped);
582
+ const noSources = sources.length === 0;
583
+ const deferred = noSources && coverageOwnsIt;
576
584
  checks.push({
577
585
  name: "Sources configured",
578
- ok: sources.length > 0,
579
- detail: sources.length > 0 ? `${sources.length} source(s) to mirror.` : "No `source.contacts` — nothing to sync."
586
+ ok: !noSources,
587
+ skipped: deferred,
588
+ detail: sourcesDetail(sources.length, deferred)
580
589
  });
581
- checks.push(checkConceptCoverage(config));
590
+ checks.push(coverage);
582
591
  checks.push(...cursorColumnChecks(sources));
583
592
  for (const source of sources) {
584
593
  const check = cursorMonotonicityCheck(source);
@@ -1445,6 +1454,15 @@ async function handlePreviewRequest(config, preview) {
1445
1454
  sampledCount: rows.length
1446
1455
  }, 200);
1447
1456
  }
1457
+ async function handleProjectionPreviewRequest(config, request) {
1458
+ const run = config.introspector?.runProjection;
1459
+ if (!run) return jsonResponse({ error: "This app cannot preview derived objects. Set `introspector: drizzleIntrospector(db)` in clivly.config.ts, on a clivly version that supports runProjection." }, 400);
1460
+ const rows = await run.call(config.introspector, request.projection, { limit: request.limit });
1461
+ return jsonResponse({
1462
+ rows,
1463
+ sampledCount: rows.length
1464
+ }, 200);
1465
+ }
1448
1466
  /**
1449
1467
  * Create the Clivly SDK. The implemented surface — heartbeat/discovery reporting
1450
1468
  * and the auth-verify handler — connects a developer's app to a running Clivly
@@ -1574,13 +1592,17 @@ function createClivlySDK(config) {
1574
1592
  lastPushFailure = null;
1575
1593
  if (!await syncOne(resolved.source, resolved.path, "delta", resolved.objectType, runId, SAMPLE_BATCH_SIZE, SAMPLE_BATCH_SIZE, false)) throw new Error(describePushFailure([resolved.source.entity]));
1576
1594
  };
1577
- const sendHeartbeat = () => {
1578
- const triggerUrl = resolveTriggerUrl({
1595
+ const describeSyncTrigger = () => ({
1596
+ configured: config.syncTrigger?.url,
1597
+ resolved: resolveTriggerUrl({
1579
1598
  configuredUrl: config.syncTrigger?.url,
1580
1599
  configuredPath: config.syncTrigger?.path,
1581
1600
  env: config.env ?? process.env,
1582
1601
  observedUrl
1583
- });
1602
+ })
1603
+ });
1604
+ const sendHeartbeat = () => {
1605
+ const triggerUrl = describeSyncTrigger().resolved;
1584
1606
  return fetcher(`${apiUrl}/clivly/sdk/heartbeat`, {
1585
1607
  method: "POST",
1586
1608
  headers: {
@@ -1808,6 +1830,7 @@ function createClivlySDK(config) {
1808
1830
  const startedAt = Date.now();
1809
1831
  try {
1810
1832
  if (payload.preview) return await handlePreviewRequest(config, payload.preview);
1833
+ if (payload.previewProjection) return await handleProjectionPreviewRequest(config, payload.previewProjection);
1811
1834
  if (payload.sample) await runSampleSync(payload.sample.entityKey, payload.runId);
1812
1835
  else await runScheduled({
1813
1836
  mode: payload.mode,
@@ -1903,6 +1926,7 @@ function createClivlySDK(config) {
1903
1926
  connect,
1904
1927
  ping,
1905
1928
  doctor,
1929
+ describeSyncTrigger,
1906
1930
  runScheduled,
1907
1931
  createClivlyHandler,
1908
1932
  sync: runSync,
@@ -1,4 +1,4 @@
1
- import { ClivlyEntitiesConfig, IntegrationState } from "@clivly/core";
1
+ import { ClivlyEntitiesConfig, IntegrationState, ProjectionMapping } from "@clivly/core";
2
2
  //#region src/drizzle-meta.d.ts
3
3
  declare const DRIZZLE_META: "__clivly_drizzle_source_meta__";
4
4
  interface DrizzleSourceMeta {
@@ -18,6 +18,23 @@ interface DrizzleSourceMeta {
18
18
  interface NamespaceIntrospector {
19
19
  /** All base table names in the reachable schema. */
20
20
  listTables(): Promise<string[]>;
21
+ /**
22
+ * Read a few rows through an unsaved projection, for the dashboard's derived
23
+ * object preview. Rows are keyed by field name.
24
+ *
25
+ * Optional because it is the only capability here that needs more than a
26
+ * table name, and because an introspector may be hand-written. Absent, the
27
+ * dashboard says preview is unavailable rather than failing — see
28
+ * `previewProjection`.
29
+ *
30
+ * This is what makes preview cost the host an SDK upgrade and nothing else:
31
+ * `drizzleIntrospector(db)` already closes over the connection, so a config
32
+ * that set `introspector` for the "Namespace reachable" check gains preview
33
+ * without touching a line.
34
+ */
35
+ runProjection?(projection: ProjectionMapping, opts: {
36
+ limit: number;
37
+ }): Promise<Record<string, unknown>[]>;
21
38
  /** Prove a table is genuinely selectable (SELECT ... LIMIT 1). Throws if unreachable/denied. */
22
39
  sampleSelect(table: string): Promise<void>;
23
40
  }
@@ -347,6 +364,20 @@ interface CursorStore {
347
364
  get(entity: string): Promise<SyncCursor | null> | SyncCursor | null;
348
365
  set(entity: string, cursor: SyncCursor | null): Promise<void> | void;
349
366
  }
367
+ /** @see ClivlySDK.describeSyncTrigger */
368
+ interface SyncTriggerDescription {
369
+ /**
370
+ * `syncTrigger.url` exactly as configured, placeholder and all. The raw
371
+ * value, so a caller can say what is in the file rather than what survived
372
+ * resolution.
373
+ */
374
+ configured?: string;
375
+ /**
376
+ * The URL that would be reported to Clivly on the next heartbeat, or
377
+ * `undefined` when nothing resolves. An unedited placeholder never resolves.
378
+ */
379
+ resolved?: string;
380
+ }
350
381
  interface ClivlySDK {
351
382
  /**
352
383
  * Send a single heartbeat and report the outcome richly: `ok`, the HTTP
@@ -382,6 +413,18 @@ interface ClivlySDK {
382
413
  * anyone who can reach it — set a secret if it's publicly routable.
383
414
  */
384
415
  createClivlyHandler(): (req: Request) => Promise<Response>;
416
+ /**
417
+ * What this app would tell Clivly about its sync trigger route, right now.
418
+ *
419
+ * Computed per call rather than frozen at creation: `resolved` can start as
420
+ * `undefined` and become a real URL once the mounted route has served its
421
+ * first request and observed its own origin.
422
+ *
423
+ * Exists for `clivly status`, which has to tell "no trigger configured"
424
+ * apart from "configured, but still the scaffolded placeholder" — a
425
+ * distinction `resolved` alone erases, since it skips placeholders by design.
426
+ */
427
+ describeSyncTrigger(): SyncTriggerDescription;
385
428
  /** Validate this setup end-to-end (read-only) before the first sync. */
386
429
  doctor(): Promise<DoctorReport>;
387
430
  /**
@@ -454,4 +497,4 @@ interface ClivlySDK {
454
497
  }): Promise<void>;
455
498
  }
456
499
  //#endregion
457
- export { NamespaceIntrospector as _, ClivlySDK as a, CreateChatSessionHandlerOptions as c, DoctorCheck as d, DoctorReport as f, SyncSource as g, EnsuredObjectTypeResult as h, ClivlyConnectResult as i, CursorStore as l, EnsuredMappingResult as m, ChatWidgetVisitor as n, ClivlySDKConfig as o, EnsureSchemaResult as p, ClivlyConnectReason as r, ClivlyUser as s, ChatSessionRequestBody as t, DiscoveredTable as u };
500
+ export { SyncTriggerDescription as _, ClivlySDK as a, CreateChatSessionHandlerOptions as c, DoctorCheck as d, DoctorReport as f, SyncSource as g, EnsuredObjectTypeResult as h, ClivlyConnectResult as i, CursorStore as l, EnsuredMappingResult as m, ChatWidgetVisitor as n, ClivlySDKConfig as o, EnsureSchemaResult as p, ClivlyConnectReason as r, ClivlyUser as s, ChatSessionRequestBody as t, DiscoveredTable as u, NamespaceIntrospector as v };
@@ -1,4 +1,4 @@
1
- import { ClivlyEntitiesConfig, IntegrationState } from "@clivly/core";
1
+ import { ClivlyEntitiesConfig, IntegrationState, ProjectionMapping } from "@clivly/core";
2
2
  //#region src/drizzle-meta.d.ts
3
3
  declare const DRIZZLE_META: "__clivly_drizzle_source_meta__";
4
4
  interface DrizzleSourceMeta {
@@ -18,6 +18,23 @@ interface DrizzleSourceMeta {
18
18
  interface NamespaceIntrospector {
19
19
  /** All base table names in the reachable schema. */
20
20
  listTables(): Promise<string[]>;
21
+ /**
22
+ * Read a few rows through an unsaved projection, for the dashboard's derived
23
+ * object preview. Rows are keyed by field name.
24
+ *
25
+ * Optional because it is the only capability here that needs more than a
26
+ * table name, and because an introspector may be hand-written. Absent, the
27
+ * dashboard says preview is unavailable rather than failing — see
28
+ * `previewProjection`.
29
+ *
30
+ * This is what makes preview cost the host an SDK upgrade and nothing else:
31
+ * `drizzleIntrospector(db)` already closes over the connection, so a config
32
+ * that set `introspector` for the "Namespace reachable" check gains preview
33
+ * without touching a line.
34
+ */
35
+ runProjection?(projection: ProjectionMapping, opts: {
36
+ limit: number;
37
+ }): Promise<Record<string, unknown>[]>;
21
38
  /** Prove a table is genuinely selectable (SELECT ... LIMIT 1). Throws if unreachable/denied. */
22
39
  sampleSelect(table: string): Promise<void>;
23
40
  }
@@ -347,6 +364,20 @@ interface CursorStore {
347
364
  get(entity: string): Promise<SyncCursor | null> | SyncCursor | null;
348
365
  set(entity: string, cursor: SyncCursor | null): Promise<void> | void;
349
366
  }
367
+ /** @see ClivlySDK.describeSyncTrigger */
368
+ interface SyncTriggerDescription {
369
+ /**
370
+ * `syncTrigger.url` exactly as configured, placeholder and all. The raw
371
+ * value, so a caller can say what is in the file rather than what survived
372
+ * resolution.
373
+ */
374
+ configured?: string;
375
+ /**
376
+ * The URL that would be reported to Clivly on the next heartbeat, or
377
+ * `undefined` when nothing resolves. An unedited placeholder never resolves.
378
+ */
379
+ resolved?: string;
380
+ }
350
381
  interface ClivlySDK {
351
382
  /**
352
383
  * Send a single heartbeat and report the outcome richly: `ok`, the HTTP
@@ -382,6 +413,18 @@ interface ClivlySDK {
382
413
  * anyone who can reach it — set a secret if it's publicly routable.
383
414
  */
384
415
  createClivlyHandler(): (req: Request) => Promise<Response>;
416
+ /**
417
+ * What this app would tell Clivly about its sync trigger route, right now.
418
+ *
419
+ * Computed per call rather than frozen at creation: `resolved` can start as
420
+ * `undefined` and become a real URL once the mounted route has served its
421
+ * first request and observed its own origin.
422
+ *
423
+ * Exists for `clivly status`, which has to tell "no trigger configured"
424
+ * apart from "configured, but still the scaffolded placeholder" — a
425
+ * distinction `resolved` alone erases, since it skips placeholders by design.
426
+ */
427
+ describeSyncTrigger(): SyncTriggerDescription;
385
428
  /** Validate this setup end-to-end (read-only) before the first sync. */
386
429
  doctor(): Promise<DoctorReport>;
387
430
  /**
@@ -454,4 +497,4 @@ interface ClivlySDK {
454
497
  }): Promise<void>;
455
498
  }
456
499
  //#endregion
457
- export { NamespaceIntrospector as _, ClivlySDK as a, CreateChatSessionHandlerOptions as c, DoctorCheck as d, DoctorReport as f, SyncSource as g, EnsuredObjectTypeResult as h, ClivlyConnectResult as i, CursorStore as l, EnsuredMappingResult as m, ChatWidgetVisitor as n, ClivlySDKConfig as o, EnsureSchemaResult as p, ClivlyConnectReason as r, ClivlyUser as s, ChatSessionRequestBody as t, DiscoveredTable as u };
500
+ export { SyncTriggerDescription as _, ClivlySDK as a, CreateChatSessionHandlerOptions as c, DoctorCheck as d, DoctorReport as f, SyncSource as g, EnsuredObjectTypeResult as h, ClivlyConnectResult as i, CursorStore as l, EnsuredMappingResult as m, ChatWidgetVisitor as n, ClivlySDKConfig as o, EnsureSchemaResult as p, ClivlyConnectReason as r, ClivlyUser as s, ChatSessionRequestBody as t, DiscoveredTable as u, NamespaceIntrospector as v };
package/dist/vite.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as ClivlySDK } from "./types-Dz1LQYvQ.cjs";
1
+ import { a as ClivlySDK } from "./types-CiFEalbP.cjs";
2
2
  import { Plugin } from "vite";
3
3
  //#region src/vite.d.ts
4
4
  interface ClivlyViteOptions {
package/dist/vite.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as ClivlySDK } from "./types-Dz1LQYvQ.mjs";
1
+ import { a as ClivlySDK } from "./types-CiFEalbP.mjs";
2
2
  import { Plugin } from "vite";
3
3
  //#region src/vite.d.ts
4
4
  interface ClivlyViteOptions {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clivly/sdk",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Clivly SDK — connect your app to Clivly Cloud (heartbeat, discovery, auth bridge).",
@@ -55,7 +55,7 @@
55
55
  "access": "public"
56
56
  },
57
57
  "dependencies": {
58
- "@clivly/core": "0.9.5"
58
+ "@clivly/core": "0.9.6"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "drizzle-orm": ">=0.30 || >=1.0.0-rc.0",