@clivly/sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -24
- package/dist/drizzle-meta-C0T6F3vL.cjs +9 -0
- package/dist/drizzle-meta-CAJ43cLc.mjs +4 -0
- package/dist/drizzle.cjs +50 -0
- package/dist/drizzle.d.cts +22 -0
- package/dist/drizzle.d.mts +22 -0
- package/dist/drizzle.mjs +49 -0
- package/dist/index.cjs +494 -0
- package/dist/index.d.cts +33 -0
- package/dist/index.d.mts +19 -124
- package/dist/index.mjs +223 -20
- package/dist/types-B0aSxJ6V.d.cts +224 -0
- package/dist/types-DWt-ytBQ.d.mts +224 -0
- package/package.json +34 -8
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { ClivlyEntitiesConfig } from "@clivly/core";
|
|
2
|
+
|
|
3
|
+
//#region src/drizzle-meta.d.ts
|
|
4
|
+
declare const DRIZZLE_META: unique symbol;
|
|
5
|
+
interface DrizzleSourceMeta {
|
|
6
|
+
/** Drizzle property keys of every column on the source table. */
|
|
7
|
+
columnKeys: string[];
|
|
8
|
+
cursorField: string;
|
|
9
|
+
idField: string;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/types.d.ts
|
|
13
|
+
interface DiscoveredTable {
|
|
14
|
+
columns: string[];
|
|
15
|
+
name: string;
|
|
16
|
+
}
|
|
17
|
+
interface ClivlySDKConfig {
|
|
18
|
+
/** Org-scoped API token (sk_live_…) from Settings → API keys. */
|
|
19
|
+
apiKey: string;
|
|
20
|
+
/** Clivly API base URL. Defaults to https://api.clivly.com. */
|
|
21
|
+
apiUrl?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The host table Clivly treats as the contact entity.
|
|
24
|
+
* @deprecated Prefer `entities` — the same mapping-derived shape
|
|
25
|
+
* `crm_entity_mappings` (and `@clivly/core`'s `mappingsToEntitiesConfig`)
|
|
26
|
+
* produce, so the SDK, the mappings table, and the sync engine share one
|
|
27
|
+
* model. A bare `contactEntity` is treated as a single contact entity.
|
|
28
|
+
*/
|
|
29
|
+
contactEntity?: string;
|
|
30
|
+
/** Which columns of the contact entity Clivly may read. */
|
|
31
|
+
contactFields?: string[];
|
|
32
|
+
/**
|
|
33
|
+
* Where per-entity delta cursors are persisted between syncs. Defaults to an
|
|
34
|
+
* in-memory store (fine for a long-lived process). On serverless/edge, pass a
|
|
35
|
+
* store backed by your own KV/DB so a cold start resumes delta sync instead of
|
|
36
|
+
* re-paging the whole entity. See `CursorStore`.
|
|
37
|
+
*/
|
|
38
|
+
cursorStore?: CursorStore;
|
|
39
|
+
/**
|
|
40
|
+
* Declarative entity config — the single source of truth shared with
|
|
41
|
+
* `crm_entity_mappings`. When set, discovery and heartbeat are derived from
|
|
42
|
+
* it (each entity's `source` table + mapped columns), superseding the flat
|
|
43
|
+
* `contactEntity`.
|
|
44
|
+
*/
|
|
45
|
+
entities?: ClivlyEntitiesConfig;
|
|
46
|
+
/**
|
|
47
|
+
* 🚧 Not implemented in 0.1.x. Reserved options for tunnel mode and CRM
|
|
48
|
+
* write-back — they have NO effect yet and land with the Cloudflare
|
|
49
|
+
* infrastructure. Grouped under `experimental` so the stable config surface
|
|
50
|
+
* above is unambiguous; nothing here is part of the supported API.
|
|
51
|
+
*/
|
|
52
|
+
experimental?: {
|
|
53
|
+
/** Cloudflare Tunnel token — reserved for tunnel mode. Not wired up yet. */tunnelToken?: string; /** CRM write-back target — reserved. Not wired up yet. */
|
|
54
|
+
writeBack?: {
|
|
55
|
+
enabled?: boolean; /** Only crm_* tables are ever accepted; this narrows further. */
|
|
56
|
+
tables?: string[];
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/** Reported to the portal so it can show the detected stack. */
|
|
60
|
+
framework?: string;
|
|
61
|
+
/** Heartbeat cadence in ms. Defaults to 60s. */
|
|
62
|
+
heartbeatIntervalMs?: number;
|
|
63
|
+
/** Whether the crm_* namespace exists in the host DB (self-reported). */
|
|
64
|
+
namespaceReady?: boolean;
|
|
65
|
+
orm?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Network retry policy for outbound calls (heartbeat + sync push). Transient
|
|
68
|
+
* failures (network errors, HTTP 429, HTTP 5xx) are retried with exponential
|
|
69
|
+
* backoff + jitter; a `Retry-After` header on a 429 is honoured. Non-transient
|
|
70
|
+
* responses (e.g. 4xx other than 429) are returned immediately, not retried.
|
|
71
|
+
*/
|
|
72
|
+
retry?: {
|
|
73
|
+
/** Total attempts including the first. Defaults to 3. Min 1. */maxAttempts?: number; /** Base backoff in ms (doubled each attempt). Defaults to 500. */
|
|
74
|
+
baseDelayMs?: number;
|
|
75
|
+
};
|
|
76
|
+
/** Tables/columns to report for entity discovery + mapping. */
|
|
77
|
+
schema?: DiscoveredTable[];
|
|
78
|
+
sdkVersion?: string;
|
|
79
|
+
/** Host entities to mirror into Clivly Cloud. */
|
|
80
|
+
source?: {
|
|
81
|
+
contacts: SyncSource;
|
|
82
|
+
companies?: SyncSource;
|
|
83
|
+
};
|
|
84
|
+
sync?: {
|
|
85
|
+
onBoot?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Sync cadence in ms for the background keep-alive loop. Defaults to
|
|
88
|
+
* `heartbeatIntervalMs` (60s) so sync and heartbeat share a tick unless you
|
|
89
|
+
* decouple them — e.g. a slow full-mirror alongside frequent heartbeats.
|
|
90
|
+
*/
|
|
91
|
+
intervalMs?: number;
|
|
92
|
+
batchSize?: number;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Shared secret Clivly Cloud presents (as `Authorization: Bearer <token>`) when
|
|
96
|
+
* calling the `/clivly/auth/verify` endpoint. Required for the verify handler to
|
|
97
|
+
* authenticate the caller; without it the handler fails closed (HTTP 503).
|
|
98
|
+
*/
|
|
99
|
+
verifyToken?: string;
|
|
100
|
+
}
|
|
101
|
+
interface ClivlyUser {
|
|
102
|
+
email: string;
|
|
103
|
+
id: string;
|
|
104
|
+
name?: string;
|
|
105
|
+
}
|
|
106
|
+
interface DoctorCheck {
|
|
107
|
+
/** Human-readable outcome — what passed, or why it failed. */
|
|
108
|
+
detail: string;
|
|
109
|
+
/** Short label, e.g. "API key present". */
|
|
110
|
+
name: string;
|
|
111
|
+
/** True when the check passed. Ignored when `skipped` is true. */
|
|
112
|
+
ok: boolean;
|
|
113
|
+
/** True when the check could not run (e.g. a hand-written source). */
|
|
114
|
+
skipped?: boolean;
|
|
115
|
+
}
|
|
116
|
+
interface DoctorReport {
|
|
117
|
+
checks: DoctorCheck[];
|
|
118
|
+
/** AND of every non-skipped check's `ok`. */
|
|
119
|
+
ok: boolean;
|
|
120
|
+
}
|
|
121
|
+
type ClivlyConnectReason = "ok" | "invalid_api_key" | "forbidden" | "rate_limited" | "server_error" | "client_error" | "network_error";
|
|
122
|
+
interface ClivlyConnectResult {
|
|
123
|
+
ok: boolean;
|
|
124
|
+
/** Org resolved from a successful heartbeat, when the server returns it. */
|
|
125
|
+
org?: {
|
|
126
|
+
id?: string;
|
|
127
|
+
name?: string;
|
|
128
|
+
};
|
|
129
|
+
reason: ClivlyConnectReason;
|
|
130
|
+
/** Whether a retry could plausibly succeed (network, 429, 5xx). */
|
|
131
|
+
retryable: boolean;
|
|
132
|
+
/** HTTP status, or null when the request never completed (network error). */
|
|
133
|
+
status: number | null;
|
|
134
|
+
}
|
|
135
|
+
interface SyncCursor {
|
|
136
|
+
/** The row's id, breaking ties when timestamps collide. */
|
|
137
|
+
id: string;
|
|
138
|
+
/** The row's cursor-field value (typically `updated_at`). */
|
|
139
|
+
time: Date;
|
|
140
|
+
}
|
|
141
|
+
interface SyncSource {
|
|
142
|
+
cursorField: string;
|
|
143
|
+
entity: string;
|
|
144
|
+
fetchPage(args: {
|
|
145
|
+
since: SyncCursor | null;
|
|
146
|
+
limit: number;
|
|
147
|
+
}): Promise<Record<string, unknown>[]>;
|
|
148
|
+
/** Stable, unique tie-breaker column for keyset paging. Defaults to "id". */
|
|
149
|
+
idField?: string;
|
|
150
|
+
/** Set by `fromDrizzle`; lets `doctor` introspect the backing table. */
|
|
151
|
+
[DRIZZLE_META]?: DrizzleSourceMeta;
|
|
152
|
+
}
|
|
153
|
+
type SyncMode = "delta" | "full";
|
|
154
|
+
interface CursorStore {
|
|
155
|
+
get(entity: string): Promise<SyncCursor | null> | SyncCursor | null;
|
|
156
|
+
set(entity: string, cursor: SyncCursor | null): Promise<void> | void;
|
|
157
|
+
}
|
|
158
|
+
interface ClivlySDK {
|
|
159
|
+
/**
|
|
160
|
+
* Send a single heartbeat and report the outcome richly: `ok`, the HTTP
|
|
161
|
+
* `status` (null on network failure), a coarse `reason`, whether it's
|
|
162
|
+
* `retryable`, and the resolved `org` when the server returns it. Never throws
|
|
163
|
+
* — inspect the result, or turn a failure into an exception with
|
|
164
|
+
* `ClivlyError.from(result)`. Prefer this over `heartbeat()` for onboarding,
|
|
165
|
+
* health checks, and one-shot serverless calls.
|
|
166
|
+
*/
|
|
167
|
+
connect(): Promise<ClivlyConnectResult>;
|
|
168
|
+
/**
|
|
169
|
+
* Build the `/clivly/auth/verify` handler the developer mounts in their app.
|
|
170
|
+
* Clivly's cloud calls it to validate a CRM user's identity against the host
|
|
171
|
+
* app's session. Pass a resolver that reads the host session from the request.
|
|
172
|
+
*
|
|
173
|
+
* The handler authenticates the caller against `config.verifyToken` (presented
|
|
174
|
+
* as `Authorization: Bearer <token>`) before invoking the resolver, so the
|
|
175
|
+
* endpoint can't be used by third parties to probe host sessions. It fails
|
|
176
|
+
* closed with HTTP 503 if `verifyToken` is unset (a missing config value, not a
|
|
177
|
+
* server fault — so it won't trip the host's 5xx error alerting).
|
|
178
|
+
*/
|
|
179
|
+
createAuthVerifyHandler(resolveUser: (req: Request) => Promise<ClivlyUser | null> | ClivlyUser | null): (req: Request) => Promise<Response>;
|
|
180
|
+
/**
|
|
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()`.
|
|
185
|
+
*/
|
|
186
|
+
createClivlyHandler(): (req: Request) => Promise<Response>;
|
|
187
|
+
/** Validate this setup end-to-end (read-only) before the first sync. */
|
|
188
|
+
doctor(): Promise<DoctorReport>;
|
|
189
|
+
/**
|
|
190
|
+
* Send a single heartbeat now. Returns true on success. Kept for the keep-alive
|
|
191
|
+
* loop and back-compat; use `connect()` when you need the failure reason.
|
|
192
|
+
*/
|
|
193
|
+
heartbeat(): Promise<boolean>;
|
|
194
|
+
/**
|
|
195
|
+
* One-shot heartbeat + a single sync, with no keep-alive timers. The correct
|
|
196
|
+
* entry point on serverless/edge (Workers, Vercel, Lambda), where `start()`'s
|
|
197
|
+
* `setInterval` loop can't persist between invocations — call this from a
|
|
198
|
+
* scheduled/cron trigger. Throws if a sync push fails (so the run is marked
|
|
199
|
+
* failed); heartbeat is best-effort.
|
|
200
|
+
*/
|
|
201
|
+
runScheduled(options?: {
|
|
202
|
+
mode?: SyncMode;
|
|
203
|
+
}): Promise<void>;
|
|
204
|
+
/**
|
|
205
|
+
* Send the initial heartbeat and start the keep-alive loop. For long-lived
|
|
206
|
+
* hosts only — on serverless/edge the interval can't persist, so prefer
|
|
207
|
+
* `runScheduled()` / `createClivlyHandler()`. Emits a warning if a serverless
|
|
208
|
+
* runtime is detected.
|
|
209
|
+
*/
|
|
210
|
+
start(): Promise<void>;
|
|
211
|
+
/** Stop the keep-alive loop. */
|
|
212
|
+
stop(): void;
|
|
213
|
+
/**
|
|
214
|
+
* Sync all configured sources now. `delta` (default) pages changed rows since
|
|
215
|
+
* the last cursor; `full` pushes each complete entity so the server can
|
|
216
|
+
* reconcile leavers (archive-on-leave). Throws if a push fails, so callers see
|
|
217
|
+
* the failure — the background keep-alive loop swallows and retries instead.
|
|
218
|
+
*/
|
|
219
|
+
sync(options?: {
|
|
220
|
+
mode?: SyncMode;
|
|
221
|
+
}): Promise<void>;
|
|
222
|
+
}
|
|
223
|
+
//#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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clivly/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Clivly SDK — connect your app to Clivly Cloud (heartbeat, discovery, auth bridge).",
|
|
@@ -8,8 +8,24 @@
|
|
|
8
8
|
"sideEffects": false,
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.mts",
|
|
13
|
+
"default": "./dist/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"./drizzle": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/drizzle.d.mts",
|
|
23
|
+
"default": "./dist/drizzle.mjs"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/drizzle.d.cts",
|
|
27
|
+
"default": "./dist/drizzle.cjs"
|
|
28
|
+
}
|
|
13
29
|
}
|
|
14
30
|
},
|
|
15
31
|
"files": [
|
|
@@ -19,19 +35,29 @@
|
|
|
19
35
|
"access": "public"
|
|
20
36
|
},
|
|
21
37
|
"dependencies": {
|
|
22
|
-
"@clivly/core": "0.
|
|
38
|
+
"@clivly/core": "0.2.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"drizzle-orm": ">=0.30"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"drizzle-orm": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
23
47
|
},
|
|
24
48
|
"devDependencies": {
|
|
49
|
+
"@electric-sql/pglite": "^0.5.4",
|
|
50
|
+
"drizzle-orm": "^0.45.2",
|
|
25
51
|
"tsdown": "^0.21.9",
|
|
26
52
|
"typescript": "^6",
|
|
27
|
-
"vitest": "^3.2.0"
|
|
28
|
-
"@clivly.com/config": "0.0.0"
|
|
53
|
+
"vitest": "^3.2.0"
|
|
29
54
|
},
|
|
30
55
|
"scripts": {
|
|
31
56
|
"build": "tsdown",
|
|
32
57
|
"check-types": "tsc --noEmit",
|
|
33
58
|
"test": "vitest run"
|
|
34
59
|
},
|
|
35
|
-
"main": "./dist/index.
|
|
36
|
-
"
|
|
60
|
+
"main": "./dist/index.cjs",
|
|
61
|
+
"module": "./dist/index.mjs",
|
|
62
|
+
"types": "./dist/index.d.cts"
|
|
37
63
|
}
|