@clivly/sdk 0.2.0 → 0.3.0-next.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.
@@ -1,18 +1,39 @@
1
1
  import { ClivlyEntitiesConfig } from "@clivly/core";
2
2
 
3
3
  //#region src/drizzle-meta.d.ts
4
- declare const DRIZZLE_META: unique symbol;
4
+ declare const DRIZZLE_META: "__clivly_drizzle_source_meta__";
5
5
  interface DrizzleSourceMeta {
6
6
  /** Drizzle property keys of every column on the source table. */
7
7
  columnKeys: string[];
8
+ /** Schema-v2 typed column metadata derived from the Drizzle table. */
9
+ columnsMeta?: DiscoveredColumn[];
8
10
  cursorField: string;
9
11
  idField: string;
12
+ /** The backing table's real name, so discovery can key metadata by table. */
13
+ tableName?: string;
10
14
  }
11
15
  //#endregion
12
16
  //#region src/types.d.ts
17
+ interface DiscoveredColumn {
18
+ isForeignKey?: boolean;
19
+ isPrimaryKey?: boolean;
20
+ name: string;
21
+ nullable?: boolean;
22
+ /** The column this FK points at, when it is a foreign key. */
23
+ references?: {
24
+ table: string;
25
+ column: string;
26
+ };
27
+ /** SQL/driver type, e.g. "text", "integer", "timestamp". */
28
+ type?: string;
29
+ }
13
30
  interface DiscoveredTable {
14
31
  columns: string[];
32
+ /** Schema-v2 typed column metadata (types, PK/FK, nullability). */
33
+ columnsMeta?: DiscoveredColumn[];
15
34
  name: string;
35
+ /** Approximate source row count, when cheaply derivable. */
36
+ rowCount?: number;
16
37
  }
17
38
  interface ClivlySDKConfig {
18
39
  /** Org-scoped API token (sk_live_…) from Settings → API keys. */
@@ -60,7 +81,18 @@ interface ClivlySDKConfig {
60
81
  framework?: string;
61
82
  /** Heartbeat cadence in ms. Defaults to 60s. */
62
83
  heartbeatIntervalMs?: number;
63
- /** Whether the crm_* namespace exists in the host DB (self-reported). */
84
+ /**
85
+ * Optional live namespace probe, used on-demand by `doctor()`'s "Namespace
86
+ * reachable" check to verify every required source table + column is
87
+ * genuinely reachable. Not consulted by the heartbeat. Build one with
88
+ * `drizzleIntrospector(db)` from `@clivly/sdk/drizzle`.
89
+ */
90
+ introspector?: NamespaceIntrospector;
91
+ /**
92
+ * Self-reported readiness sent on every heartbeat as-is (no live check).
93
+ * For a real reachability check, configure `introspector` and run
94
+ * `doctor()`.
95
+ */
64
96
  namespaceReady?: boolean;
65
97
  orm?: string;
66
98
  /**
@@ -80,6 +112,10 @@ interface ClivlySDKConfig {
80
112
  source?: {
81
113
  contacts: SyncSource;
82
114
  companies?: SyncSource;
115
+ deals?: SyncSource; /** Org-defined custom objects to mirror. Each carries its object-type slug. */
116
+ custom?: Array<SyncSource & {
117
+ objectType: string;
118
+ }>;
83
119
  };
84
120
  sync?: {
85
121
  onBoot?: boolean;
@@ -91,6 +127,29 @@ interface ClivlySDKConfig {
91
127
  intervalMs?: number;
92
128
  batchSize?: number;
93
129
  };
130
+ /**
131
+ * The remote sync trigger: how Clivly Cloud asks this app to run a sync.
132
+ *
133
+ * Without it, cloud mode is push-only — Clivly can watch the app but can't
134
+ * start anything, so the dashboard's "Run sync" has nothing to call. With it,
135
+ * the SDK reports `url` on each heartbeat and requires every trigger request to
136
+ * be signed with `secret`.
137
+ */
138
+ syncTrigger?: {
139
+ /**
140
+ * Public URL of the route where `createClivlyHandler()` is mounted, e.g.
141
+ * `https://app.example.com/api/clivly/tick`. Reported to Clivly on each
142
+ * heartbeat; a URL set in the dashboard overrides it.
143
+ */
144
+ url?: string;
145
+ /**
146
+ * Signing secret (`whsec_…`) issued by Clivly under Integrations → Remote
147
+ * sync. When set, `createClivlyHandler()` rejects any request that isn't
148
+ * validly signed. Leave unset only if the route is already unreachable from
149
+ * the public internet.
150
+ */
151
+ secret?: string;
152
+ };
94
153
  /**
95
154
  * Shared secret Clivly Cloud presents (as `Authorization: Bearer <token>`) when
96
155
  * calling the `/clivly/auth/verify` endpoint. Required for the verify handler to
@@ -178,10 +237,16 @@ interface ClivlySDK {
178
237
  */
179
238
  createAuthVerifyHandler(resolveUser: (req: Request) => Promise<ClivlyUser | null> | ClivlyUser | null): (req: Request) => Promise<Response>;
180
239
  /**
181
- * A fetch handler to mount at a route and drive from an external scheduler
182
- * (Cloudflare Workers cron, an uptime ping, etc.). Each request runs one
183
- * `runScheduled()` and returns JSON: 200 when the sync succeeded, 500 otherwise.
184
- * The ergonomic serverless entry point alongside `runScheduled()`.
240
+ * A fetch handler to mount at a route. Each request runs one `runScheduled()`
241
+ * and returns JSON: 200 when the sync succeeded, 500 otherwise.
242
+ *
243
+ * Two callers use it: Clivly Cloud (when someone clicks "Run sync" — those
244
+ * requests are HMAC-signed and carry a run id), and the host's own scheduler
245
+ * (Workers cron, an uptime ping — no body).
246
+ *
247
+ * When `syncTrigger.secret` is set the handler rejects unsigned or
248
+ * badly-signed requests with 401. When it isn't, the route stays open to
249
+ * anyone who can reach it — set a secret if it's publicly routable.
185
250
  */
186
251
  createClivlyHandler(): (req: Request) => Promise<Response>;
187
252
  /** Validate this setup end-to-end (read-only) before the first sync. */
@@ -200,6 +265,7 @@ interface ClivlySDK {
200
265
  */
201
266
  runScheduled(options?: {
202
267
  mode?: SyncMode;
268
+ runId?: string;
203
269
  }): Promise<void>;
204
270
  /**
205
271
  * Send the initial heartbeat and start the keep-alive loop. For long-lived
@@ -218,7 +284,16 @@ interface ClivlySDK {
218
284
  */
219
285
  sync(options?: {
220
286
  mode?: SyncMode;
287
+ runId?: string;
221
288
  }): Promise<void>;
222
289
  }
223
290
  //#endregion
224
- export { ClivlyUser as a, DoctorCheck as c, ClivlySDKConfig as i, DoctorReport as l, ClivlyConnectResult as n, CursorStore as o, ClivlySDK as r, DiscoveredTable as s, ClivlyConnectReason as t, SyncSource as u };
291
+ //#region src/namespace-probe.d.ts
292
+ interface NamespaceIntrospector {
293
+ /** All base table names in the reachable schema. */
294
+ listTables(): Promise<string[]>;
295
+ /** Prove a table is genuinely selectable (SELECT ... LIMIT 1). Throws if unreachable/denied. */
296
+ sampleSelect(table: string): Promise<void>;
297
+ }
298
+ //#endregion
299
+ export { ClivlySDKConfig as a, DiscoveredTable as c, SyncSource as d, ClivlySDK as i, DoctorCheck as l, ClivlyConnectReason as n, ClivlyUser as o, ClivlyConnectResult as r, CursorStore as s, NamespaceIntrospector as t, DoctorReport as u };
package/dist/vite.cjs ADDED
@@ -0,0 +1,38 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/vite.ts
3
+ /**
4
+ * Vite dev-server plugin that runs the Clivly SDK lifecycle for you: on server
5
+ * boot it calls `sdk.connect()` (for a clear status line) then `sdk.start()`
6
+ * (the heartbeat + sync keep-alive loop), and calls `sdk.stop()` when the dev
7
+ * server closes. Dev-only (`apply: "serve"`) — `start()`'s setInterval loop
8
+ * does not persist on serverless/edge builds, so production on Workers/Lambda
9
+ * should mount `sdk.createClivlyHandler()` and drive it from a cron trigger
10
+ * instead. See docs/guides/connecting-to-clivly-cloud.md.
11
+ *
12
+ * Vite is an optional peer dependency imported type-only (`import type`), so
13
+ * the built bundle carries no runtime dependency on Vite.
14
+ */
15
+ function clivlyVite(sdk, options = {}) {
16
+ const label = options.name ?? "clivly";
17
+ return {
18
+ name: "clivly",
19
+ apply: "serve",
20
+ configureServer(server) {
21
+ const { logger } = server.config;
22
+ const boot = async () => {
23
+ const result = await sdk.connect();
24
+ if (result.ok) {
25
+ const org = result.org?.name ?? result.org?.id;
26
+ logger.info(`${label}: connected${org ? ` to ${org}` : ""}`);
27
+ } else logger.warn(`${label}: not connected (${result.reason})`);
28
+ await sdk.start();
29
+ };
30
+ boot().catch((error) => {
31
+ logger.error(`${label}: startup failed — ${error instanceof Error ? error.message : String(error)}`);
32
+ });
33
+ server.httpServer?.once("close", () => sdk.stop());
34
+ }
35
+ };
36
+ }
37
+ //#endregion
38
+ exports.clivlyVite = clivlyVite;
@@ -0,0 +1,23 @@
1
+ import { i as ClivlySDK } from "./namespace-probe-DIBgCQca.cjs";
2
+ import { Plugin } from "vite";
3
+
4
+ //#region src/vite.d.ts
5
+ interface ClivlyViteOptions {
6
+ /** Log prefix for Clivly's boot lines. Defaults to "clivly". */
7
+ name?: string;
8
+ }
9
+ /**
10
+ * Vite dev-server plugin that runs the Clivly SDK lifecycle for you: on server
11
+ * boot it calls `sdk.connect()` (for a clear status line) then `sdk.start()`
12
+ * (the heartbeat + sync keep-alive loop), and calls `sdk.stop()` when the dev
13
+ * server closes. Dev-only (`apply: "serve"`) — `start()`'s setInterval loop
14
+ * does not persist on serverless/edge builds, so production on Workers/Lambda
15
+ * should mount `sdk.createClivlyHandler()` and drive it from a cron trigger
16
+ * instead. See docs/guides/connecting-to-clivly-cloud.md.
17
+ *
18
+ * Vite is an optional peer dependency imported type-only (`import type`), so
19
+ * the built bundle carries no runtime dependency on Vite.
20
+ */
21
+ declare function clivlyVite(sdk: ClivlySDK, options?: ClivlyViteOptions): Plugin;
22
+ //#endregion
23
+ export { ClivlyViteOptions, clivlyVite };
@@ -0,0 +1,23 @@
1
+ import { i as ClivlySDK } from "./namespace-probe-DFpvfl7T.mjs";
2
+ import { Plugin } from "vite";
3
+
4
+ //#region src/vite.d.ts
5
+ interface ClivlyViteOptions {
6
+ /** Log prefix for Clivly's boot lines. Defaults to "clivly". */
7
+ name?: string;
8
+ }
9
+ /**
10
+ * Vite dev-server plugin that runs the Clivly SDK lifecycle for you: on server
11
+ * boot it calls `sdk.connect()` (for a clear status line) then `sdk.start()`
12
+ * (the heartbeat + sync keep-alive loop), and calls `sdk.stop()` when the dev
13
+ * server closes. Dev-only (`apply: "serve"`) — `start()`'s setInterval loop
14
+ * does not persist on serverless/edge builds, so production on Workers/Lambda
15
+ * should mount `sdk.createClivlyHandler()` and drive it from a cron trigger
16
+ * instead. See docs/guides/connecting-to-clivly-cloud.md.
17
+ *
18
+ * Vite is an optional peer dependency imported type-only (`import type`), so
19
+ * the built bundle carries no runtime dependency on Vite.
20
+ */
21
+ declare function clivlyVite(sdk: ClivlySDK, options?: ClivlyViteOptions): Plugin;
22
+ //#endregion
23
+ export { ClivlyViteOptions, clivlyVite };
package/dist/vite.mjs ADDED
@@ -0,0 +1,37 @@
1
+ //#region src/vite.ts
2
+ /**
3
+ * Vite dev-server plugin that runs the Clivly SDK lifecycle for you: on server
4
+ * boot it calls `sdk.connect()` (for a clear status line) then `sdk.start()`
5
+ * (the heartbeat + sync keep-alive loop), and calls `sdk.stop()` when the dev
6
+ * server closes. Dev-only (`apply: "serve"`) — `start()`'s setInterval loop
7
+ * does not persist on serverless/edge builds, so production on Workers/Lambda
8
+ * should mount `sdk.createClivlyHandler()` and drive it from a cron trigger
9
+ * instead. See docs/guides/connecting-to-clivly-cloud.md.
10
+ *
11
+ * Vite is an optional peer dependency imported type-only (`import type`), so
12
+ * the built bundle carries no runtime dependency on Vite.
13
+ */
14
+ function clivlyVite(sdk, options = {}) {
15
+ const label = options.name ?? "clivly";
16
+ return {
17
+ name: "clivly",
18
+ apply: "serve",
19
+ configureServer(server) {
20
+ const { logger } = server.config;
21
+ const boot = async () => {
22
+ const result = await sdk.connect();
23
+ if (result.ok) {
24
+ const org = result.org?.name ?? result.org?.id;
25
+ logger.info(`${label}: connected${org ? ` to ${org}` : ""}`);
26
+ } else logger.warn(`${label}: not connected (${result.reason})`);
27
+ await sdk.start();
28
+ };
29
+ boot().catch((error) => {
30
+ logger.error(`${label}: startup failed — ${error instanceof Error ? error.message : String(error)}`);
31
+ });
32
+ server.httpServer?.once("close", () => sdk.stop());
33
+ }
34
+ };
35
+ }
36
+ //#endregion
37
+ export { clivlyVite };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clivly/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-next.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Clivly SDK — connect your app to Clivly Cloud (heartbeat, discovery, auth bridge).",
@@ -26,6 +26,16 @@
26
26
  "types": "./dist/drizzle.d.cts",
27
27
  "default": "./dist/drizzle.cjs"
28
28
  }
29
+ },
30
+ "./vite": {
31
+ "import": {
32
+ "types": "./dist/vite.d.mts",
33
+ "default": "./dist/vite.mjs"
34
+ },
35
+ "require": {
36
+ "types": "./dist/vite.d.cts",
37
+ "default": "./dist/vite.cjs"
38
+ }
29
39
  }
30
40
  },
31
41
  "files": [
@@ -35,14 +45,18 @@
35
45
  "access": "public"
36
46
  },
37
47
  "dependencies": {
38
- "@clivly/core": "0.2.0"
48
+ "@clivly/core": "0.3.0-next.1"
39
49
  },
40
50
  "peerDependencies": {
41
- "drizzle-orm": ">=0.30"
51
+ "drizzle-orm": ">=0.30",
52
+ "vite": ">=5"
42
53
  },
43
54
  "peerDependenciesMeta": {
44
55
  "drizzle-orm": {
45
56
  "optional": true
57
+ },
58
+ "vite": {
59
+ "optional": true
46
60
  }
47
61
  },
48
62
  "devDependencies": {
@@ -50,6 +64,7 @@
50
64
  "drizzle-orm": "^0.45.2",
51
65
  "tsdown": "^0.21.9",
52
66
  "typescript": "^6",
67
+ "vite": "^8.0.8",
53
68
  "vitest": "^3.2.0"
54
69
  },
55
70
  "scripts": {