@clivly/sdk 0.3.0-next.2 → 0.3.0-next.4
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 +13 -11
- package/dist/drizzle.d.cts +1 -1
- package/dist/drizzle.d.mts +1 -1
- package/dist/index.cjs +128 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +128 -1
- package/dist/{namespace-probe-DFpvfl7T.d.mts → namespace-probe-9wTakLJu.d.cts} +17 -0
- package/dist/{namespace-probe-DIBgCQca.d.cts → namespace-probe-BD1-lqlp.d.mts} +17 -0
- package/dist/vite.d.cts +1 -1
- package/dist/vite.d.mts +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,18 +12,18 @@ This README stays focused on the SDK surface itself.
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npm install
|
|
15
|
+
npm install clivly
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Add `drizzle-orm` when you use
|
|
19
|
-
the Vite/TanStack Start plugin.
|
|
18
|
+
Add `drizzle-orm` when you use `clivly/drizzle` or `clivly/core/drizzle`, and
|
|
19
|
+
`vite` when you use the Vite/TanStack Start plugin.
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
-
import { createClivlySDK } from "
|
|
25
|
-
import { fromDrizzle } from "
|
|
26
|
-
import { defineClivlyConfig } from "
|
|
24
|
+
import { createClivlySDK } from "clivly/sdk";
|
|
25
|
+
import { fromDrizzle } from "clivly/drizzle";
|
|
26
|
+
import { defineClivlyConfig } from "clivly/core";
|
|
27
27
|
import { db, participants, accounts } from "./db";
|
|
28
28
|
|
|
29
29
|
// Declarative entity config — the non-deprecated source of truth. It maps your
|
|
@@ -64,9 +64,11 @@ const remote = createClivlySDK({
|
|
|
64
64
|
entities,
|
|
65
65
|
source: { /* … */ },
|
|
66
66
|
syncTrigger: {
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
path: "/api/clivly/tick",
|
|
68
|
+
url: process.env.CLIVLY_SYNC_TRIGGER_URL,
|
|
69
|
+
secret: process.env.CLIVLY_SYNC_TRIGGER_SECRET, // whsec_… from Integrations → Remote sync
|
|
69
70
|
},
|
|
71
|
+
verifyToken: process.env.CLIVLY_VERIFY_TOKEN,
|
|
70
72
|
});
|
|
71
73
|
|
|
72
74
|
export async function POST(request: Request) {
|
|
@@ -90,8 +92,8 @@ the app but cannot start a sync from the dashboard.
|
|
|
90
92
|
If you rotate a remote-sync secret from Clivly's dashboard after the encryption
|
|
91
93
|
rollout, Clivly stores it encrypted at rest. The server-side deployment must
|
|
92
94
|
have `CLIVLY_TRIGGER_SECRET_WRAPPING_KEY` configured, while your app continues
|
|
93
|
-
to use `
|
|
94
|
-
|
|
95
|
+
to use `CLIVLY_SYNC_TRIGGER_SECRET`. Existing plaintext rows remain valid during
|
|
96
|
+
rollout, and older rows can be backfilled with
|
|
95
97
|
`node ./scripts/backfill-sync-trigger-secrets.mjs --write`.
|
|
96
98
|
|
|
97
99
|
## Vite / TanStack Start
|
|
@@ -99,7 +101,7 @@ during rollout, and older rows can be backfilled with
|
|
|
99
101
|
For long-lived Vite dev servers, wire the SDK lifecycle through the Vite plugin:
|
|
100
102
|
|
|
101
103
|
```ts
|
|
102
|
-
import { clivlyVite } from "
|
|
104
|
+
import { clivlyVite } from "clivly/vite";
|
|
103
105
|
import clivly from "./clivly.config";
|
|
104
106
|
import { defineConfig } from "vite";
|
|
105
107
|
|
package/dist/drizzle.d.cts
CHANGED
package/dist/drizzle.d.mts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_drizzle_meta = require("./drizzle-meta-iAu8w1hj.cjs");
|
|
3
|
+
//#region src/custom-slug.ts
|
|
4
|
+
const NON_ALNUM = /[^a-z0-9]+/g;
|
|
5
|
+
const EDGE_HYPHENS = /(^-|-$)/g;
|
|
6
|
+
function slugify(value) {
|
|
7
|
+
return value.toLowerCase().trim().replace(NON_ALNUM, "-").replace(EDGE_HYPHENS, "");
|
|
8
|
+
}
|
|
9
|
+
function checkCustomObjectSlugs(config) {
|
|
10
|
+
const custom = config.source?.custom ?? [];
|
|
11
|
+
if (custom.length === 0) return {
|
|
12
|
+
name: "Custom object slugs",
|
|
13
|
+
ok: false,
|
|
14
|
+
skipped: true,
|
|
15
|
+
detail: "No custom objects configured."
|
|
16
|
+
};
|
|
17
|
+
const problems = [];
|
|
18
|
+
const seen = /* @__PURE__ */ new Map();
|
|
19
|
+
for (const source of custom) {
|
|
20
|
+
const declared = source.objectType;
|
|
21
|
+
const canonical = slugify(declared);
|
|
22
|
+
if (canonical === "") problems.push(`"${source.entity}" has an empty objectType.`);
|
|
23
|
+
else if (canonical !== declared) problems.push(`"${declared}" is not a slug — the dashboard would store it as "${canonical}".`);
|
|
24
|
+
const prior = seen.get(canonical);
|
|
25
|
+
if (prior && canonical !== "") problems.push(`duplicate slug "${canonical}" declared by "${prior}" and "${source.entity}".`);
|
|
26
|
+
else seen.set(canonical, source.entity);
|
|
27
|
+
}
|
|
28
|
+
if (problems.length > 0) return {
|
|
29
|
+
name: "Custom object slugs",
|
|
30
|
+
ok: false,
|
|
31
|
+
detail: problems.join(" ")
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
name: "Custom object slugs",
|
|
35
|
+
ok: true,
|
|
36
|
+
detail: `Ensure these object types exist in Clivly: ${custom.map((source) => source.objectType).join(", ")}.`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
3
40
|
//#region src/namespace-probe.ts
|
|
4
41
|
/**
|
|
5
42
|
* Verify the tables the integration depends on are genuinely reachable:
|
|
@@ -160,6 +197,7 @@ async function runDoctor(args) {
|
|
|
160
197
|
ok: result.ok,
|
|
161
198
|
detail: result.ok ? `Reached Clivly — org "${result.org?.name ?? "(unnamed)"}".` : `Could not connect (${result.reason}).`
|
|
162
199
|
});
|
|
200
|
+
checks.push(checkCustomObjectSlugs(config));
|
|
163
201
|
checks.push(await namespaceCheck(config));
|
|
164
202
|
return {
|
|
165
203
|
ok: checks.every((check) => check.skipped || check.ok),
|
|
@@ -258,6 +296,85 @@ async function fullSyncSource(args) {
|
|
|
258
296
|
};
|
|
259
297
|
}
|
|
260
298
|
//#endregion
|
|
299
|
+
//#region src/trigger-url.ts
|
|
300
|
+
const SCHEME_REGEX = /^https?:\/\//;
|
|
301
|
+
const INVALID_HOST_CHARS_REGEX = /[/\\@?#\s]/;
|
|
302
|
+
const PLATFORM_ORIGIN_SOURCES = [
|
|
303
|
+
{
|
|
304
|
+
key: "VERCEL_URL",
|
|
305
|
+
toOrigin: (value) => withScheme(value)
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
key: "CF_PAGES_URL",
|
|
309
|
+
toOrigin: (value) => withScheme(value)
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
key: "RAILWAY_PUBLIC_DOMAIN",
|
|
313
|
+
toOrigin: (value) => withScheme(value)
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
key: "RENDER_EXTERNAL_URL",
|
|
317
|
+
toOrigin: (value) => withScheme(value)
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
key: "FLY_APP_NAME",
|
|
321
|
+
toOrigin: (value) => `https://${value}.fly.dev`
|
|
322
|
+
}
|
|
323
|
+
];
|
|
324
|
+
function withScheme(value) {
|
|
325
|
+
return SCHEME_REGEX.test(value) ? value : `https://${value}`;
|
|
326
|
+
}
|
|
327
|
+
function stripTrailingSlash(value) {
|
|
328
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Derive the app's public origin from known platform environment variables.
|
|
332
|
+
* Returns an origin with no trailing slash, or undefined when nothing matches.
|
|
333
|
+
*/
|
|
334
|
+
function originFromPlatformEnv(env) {
|
|
335
|
+
for (const source of PLATFORM_ORIGIN_SOURCES) {
|
|
336
|
+
const raw = env[source.key]?.trim();
|
|
337
|
+
if (raw) return stripTrailingSlash(source.toOrigin(raw));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function observedTriggerUrl(req) {
|
|
341
|
+
const host = (req.headers.get("x-forwarded-host") ?? req.headers.get("host"))?.trim();
|
|
342
|
+
if (!host) return;
|
|
343
|
+
if (INVALID_HOST_CHARS_REGEX.test(host)) return;
|
|
344
|
+
const forwardedProto = req.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
345
|
+
let requestUrl;
|
|
346
|
+
try {
|
|
347
|
+
requestUrl = new URL(req.url);
|
|
348
|
+
} catch {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const proto = forwardedProto || requestUrl.protocol.replace(":", "");
|
|
352
|
+
try {
|
|
353
|
+
return new URL(`${proto}://${host}${requestUrl.pathname}`).toString();
|
|
354
|
+
} catch {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Decide which trigger URL to report, in precedence order:
|
|
360
|
+
*
|
|
361
|
+
* 1. `configuredUrl` — an explicit `syncTrigger.url`. Someone typed this on
|
|
362
|
+
* purpose, so it beats anything inferred.
|
|
363
|
+
* 2. platform env origin + `configuredPath` — available at boot, before any
|
|
364
|
+
* request has arrived. Needs the path because platform variables only give
|
|
365
|
+
* an origin.
|
|
366
|
+
* 3. `observedUrl` — accurate but only after the route has served a request.
|
|
367
|
+
*
|
|
368
|
+
* A URL set in the Clivly dashboard overrides all of these server-side; the SDK
|
|
369
|
+
* doesn't need to know about it.
|
|
370
|
+
*/
|
|
371
|
+
function resolveTriggerUrl(args) {
|
|
372
|
+
if (args.configuredUrl) return args.configuredUrl;
|
|
373
|
+
const origin = originFromPlatformEnv(args.env);
|
|
374
|
+
if (origin && args.configuredPath) return `${origin}${args.configuredPath.startsWith("/") ? args.configuredPath : `/${args.configuredPath}`}`;
|
|
375
|
+
return args.observedUrl;
|
|
376
|
+
}
|
|
377
|
+
//#endregion
|
|
261
378
|
//#region src/errors.ts
|
|
262
379
|
var ClivlyError = class ClivlyError extends Error {
|
|
263
380
|
/** HTTP status, or null when the request never completed (network error). */
|
|
@@ -548,6 +665,7 @@ function createClivlySDK(config) {
|
|
|
548
665
|
const cursorStore = config.cursorStore ?? createMemoryCursorStore();
|
|
549
666
|
const fetcher = createFetcher(config.retry);
|
|
550
667
|
let warnedUnsignedTrigger = false;
|
|
668
|
+
let observedUrl;
|
|
551
669
|
let lastPushFailure = null;
|
|
552
670
|
const createPush = (runId) => async (path, body) => {
|
|
553
671
|
const response = await fetcher(`${apiUrl}${path}`, {
|
|
@@ -632,7 +750,15 @@ function createClivlySDK(config) {
|
|
|
632
750
|
contactEntity: resolveContactEntity(config),
|
|
633
751
|
namespaceReady: config.namespaceReady ?? false,
|
|
634
752
|
schema: buildSchema(config),
|
|
635
|
-
...
|
|
753
|
+
...(() => {
|
|
754
|
+
const url = resolveTriggerUrl({
|
|
755
|
+
configuredUrl: config.syncTrigger?.url,
|
|
756
|
+
configuredPath: config.syncTrigger?.path,
|
|
757
|
+
env: config.env ?? process.env,
|
|
758
|
+
observedUrl
|
|
759
|
+
});
|
|
760
|
+
return url ? { syncTrigger: { url } } : {};
|
|
761
|
+
})()
|
|
636
762
|
})
|
|
637
763
|
});
|
|
638
764
|
const heartbeat = async () => {
|
|
@@ -686,6 +812,7 @@ function createClivlySDK(config) {
|
|
|
686
812
|
}
|
|
687
813
|
};
|
|
688
814
|
const createClivlyHandler = () => async (req) => {
|
|
815
|
+
observedUrl = observedTriggerUrl(req) ?? observedUrl;
|
|
689
816
|
const rawBody = await req.text().catch(() => "");
|
|
690
817
|
if (!await triggerRequestAllowed(req, rawBody)) return jsonResponse({
|
|
691
818
|
ok: false,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ClivlySDKConfig, c as DiscoveredTable, i as ClivlySDK, l as DoctorCheck, n as ClivlyConnectReason, o as ClivlyUser, r as ClivlyConnectResult, s as CursorStore, u as DoctorReport } from "./namespace-probe-
|
|
1
|
+
import { a as ClivlySDKConfig, c as DiscoveredTable, i as ClivlySDK, l as DoctorCheck, n as ClivlyConnectReason, o as ClivlyUser, r as ClivlyConnectResult, s as CursorStore, u as DoctorReport } from "./namespace-probe-9wTakLJu.cjs";
|
|
2
2
|
|
|
3
3
|
//#region src/errors.d.ts
|
|
4
4
|
declare class ClivlyError extends Error {
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ClivlySDKConfig, c as DiscoveredTable, i as ClivlySDK, l as DoctorCheck, n as ClivlyConnectReason, o as ClivlyUser, r as ClivlyConnectResult, s as CursorStore, u as DoctorReport } from "./namespace-probe-
|
|
1
|
+
import { a as ClivlySDKConfig, c as DiscoveredTable, i as ClivlySDK, l as DoctorCheck, n as ClivlyConnectReason, o as ClivlyUser, r as ClivlyConnectResult, s as CursorStore, u as DoctorReport } from "./namespace-probe-BD1-lqlp.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/errors.d.ts
|
|
4
4
|
declare class ClivlyError extends Error {
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,41 @@
|
|
|
1
1
|
import { t as DRIZZLE_META } from "./drizzle-meta-DMx_9nlj.mjs";
|
|
2
|
+
//#region src/custom-slug.ts
|
|
3
|
+
const NON_ALNUM = /[^a-z0-9]+/g;
|
|
4
|
+
const EDGE_HYPHENS = /(^-|-$)/g;
|
|
5
|
+
function slugify(value) {
|
|
6
|
+
return value.toLowerCase().trim().replace(NON_ALNUM, "-").replace(EDGE_HYPHENS, "");
|
|
7
|
+
}
|
|
8
|
+
function checkCustomObjectSlugs(config) {
|
|
9
|
+
const custom = config.source?.custom ?? [];
|
|
10
|
+
if (custom.length === 0) return {
|
|
11
|
+
name: "Custom object slugs",
|
|
12
|
+
ok: false,
|
|
13
|
+
skipped: true,
|
|
14
|
+
detail: "No custom objects configured."
|
|
15
|
+
};
|
|
16
|
+
const problems = [];
|
|
17
|
+
const seen = /* @__PURE__ */ new Map();
|
|
18
|
+
for (const source of custom) {
|
|
19
|
+
const declared = source.objectType;
|
|
20
|
+
const canonical = slugify(declared);
|
|
21
|
+
if (canonical === "") problems.push(`"${source.entity}" has an empty objectType.`);
|
|
22
|
+
else if (canonical !== declared) problems.push(`"${declared}" is not a slug — the dashboard would store it as "${canonical}".`);
|
|
23
|
+
const prior = seen.get(canonical);
|
|
24
|
+
if (prior && canonical !== "") problems.push(`duplicate slug "${canonical}" declared by "${prior}" and "${source.entity}".`);
|
|
25
|
+
else seen.set(canonical, source.entity);
|
|
26
|
+
}
|
|
27
|
+
if (problems.length > 0) return {
|
|
28
|
+
name: "Custom object slugs",
|
|
29
|
+
ok: false,
|
|
30
|
+
detail: problems.join(" ")
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
name: "Custom object slugs",
|
|
34
|
+
ok: true,
|
|
35
|
+
detail: `Ensure these object types exist in Clivly: ${custom.map((source) => source.objectType).join(", ")}.`
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
2
39
|
//#region src/namespace-probe.ts
|
|
3
40
|
/**
|
|
4
41
|
* Verify the tables the integration depends on are genuinely reachable:
|
|
@@ -159,6 +196,7 @@ async function runDoctor(args) {
|
|
|
159
196
|
ok: result.ok,
|
|
160
197
|
detail: result.ok ? `Reached Clivly — org "${result.org?.name ?? "(unnamed)"}".` : `Could not connect (${result.reason}).`
|
|
161
198
|
});
|
|
199
|
+
checks.push(checkCustomObjectSlugs(config));
|
|
162
200
|
checks.push(await namespaceCheck(config));
|
|
163
201
|
return {
|
|
164
202
|
ok: checks.every((check) => check.skipped || check.ok),
|
|
@@ -257,6 +295,85 @@ async function fullSyncSource(args) {
|
|
|
257
295
|
};
|
|
258
296
|
}
|
|
259
297
|
//#endregion
|
|
298
|
+
//#region src/trigger-url.ts
|
|
299
|
+
const SCHEME_REGEX = /^https?:\/\//;
|
|
300
|
+
const INVALID_HOST_CHARS_REGEX = /[/\\@?#\s]/;
|
|
301
|
+
const PLATFORM_ORIGIN_SOURCES = [
|
|
302
|
+
{
|
|
303
|
+
key: "VERCEL_URL",
|
|
304
|
+
toOrigin: (value) => withScheme(value)
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
key: "CF_PAGES_URL",
|
|
308
|
+
toOrigin: (value) => withScheme(value)
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
key: "RAILWAY_PUBLIC_DOMAIN",
|
|
312
|
+
toOrigin: (value) => withScheme(value)
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
key: "RENDER_EXTERNAL_URL",
|
|
316
|
+
toOrigin: (value) => withScheme(value)
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
key: "FLY_APP_NAME",
|
|
320
|
+
toOrigin: (value) => `https://${value}.fly.dev`
|
|
321
|
+
}
|
|
322
|
+
];
|
|
323
|
+
function withScheme(value) {
|
|
324
|
+
return SCHEME_REGEX.test(value) ? value : `https://${value}`;
|
|
325
|
+
}
|
|
326
|
+
function stripTrailingSlash(value) {
|
|
327
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Derive the app's public origin from known platform environment variables.
|
|
331
|
+
* Returns an origin with no trailing slash, or undefined when nothing matches.
|
|
332
|
+
*/
|
|
333
|
+
function originFromPlatformEnv(env) {
|
|
334
|
+
for (const source of PLATFORM_ORIGIN_SOURCES) {
|
|
335
|
+
const raw = env[source.key]?.trim();
|
|
336
|
+
if (raw) return stripTrailingSlash(source.toOrigin(raw));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function observedTriggerUrl(req) {
|
|
340
|
+
const host = (req.headers.get("x-forwarded-host") ?? req.headers.get("host"))?.trim();
|
|
341
|
+
if (!host) return;
|
|
342
|
+
if (INVALID_HOST_CHARS_REGEX.test(host)) return;
|
|
343
|
+
const forwardedProto = req.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
344
|
+
let requestUrl;
|
|
345
|
+
try {
|
|
346
|
+
requestUrl = new URL(req.url);
|
|
347
|
+
} catch {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const proto = forwardedProto || requestUrl.protocol.replace(":", "");
|
|
351
|
+
try {
|
|
352
|
+
return new URL(`${proto}://${host}${requestUrl.pathname}`).toString();
|
|
353
|
+
} catch {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Decide which trigger URL to report, in precedence order:
|
|
359
|
+
*
|
|
360
|
+
* 1. `configuredUrl` — an explicit `syncTrigger.url`. Someone typed this on
|
|
361
|
+
* purpose, so it beats anything inferred.
|
|
362
|
+
* 2. platform env origin + `configuredPath` — available at boot, before any
|
|
363
|
+
* request has arrived. Needs the path because platform variables only give
|
|
364
|
+
* an origin.
|
|
365
|
+
* 3. `observedUrl` — accurate but only after the route has served a request.
|
|
366
|
+
*
|
|
367
|
+
* A URL set in the Clivly dashboard overrides all of these server-side; the SDK
|
|
368
|
+
* doesn't need to know about it.
|
|
369
|
+
*/
|
|
370
|
+
function resolveTriggerUrl(args) {
|
|
371
|
+
if (args.configuredUrl) return args.configuredUrl;
|
|
372
|
+
const origin = originFromPlatformEnv(args.env);
|
|
373
|
+
if (origin && args.configuredPath) return `${origin}${args.configuredPath.startsWith("/") ? args.configuredPath : `/${args.configuredPath}`}`;
|
|
374
|
+
return args.observedUrl;
|
|
375
|
+
}
|
|
376
|
+
//#endregion
|
|
260
377
|
//#region src/errors.ts
|
|
261
378
|
var ClivlyError = class ClivlyError extends Error {
|
|
262
379
|
/** HTTP status, or null when the request never completed (network error). */
|
|
@@ -547,6 +664,7 @@ function createClivlySDK(config) {
|
|
|
547
664
|
const cursorStore = config.cursorStore ?? createMemoryCursorStore();
|
|
548
665
|
const fetcher = createFetcher(config.retry);
|
|
549
666
|
let warnedUnsignedTrigger = false;
|
|
667
|
+
let observedUrl;
|
|
550
668
|
let lastPushFailure = null;
|
|
551
669
|
const createPush = (runId) => async (path, body) => {
|
|
552
670
|
const response = await fetcher(`${apiUrl}${path}`, {
|
|
@@ -631,7 +749,15 @@ function createClivlySDK(config) {
|
|
|
631
749
|
contactEntity: resolveContactEntity(config),
|
|
632
750
|
namespaceReady: config.namespaceReady ?? false,
|
|
633
751
|
schema: buildSchema(config),
|
|
634
|
-
...
|
|
752
|
+
...(() => {
|
|
753
|
+
const url = resolveTriggerUrl({
|
|
754
|
+
configuredUrl: config.syncTrigger?.url,
|
|
755
|
+
configuredPath: config.syncTrigger?.path,
|
|
756
|
+
env: config.env ?? process.env,
|
|
757
|
+
observedUrl
|
|
758
|
+
});
|
|
759
|
+
return url ? { syncTrigger: { url } } : {};
|
|
760
|
+
})()
|
|
635
761
|
})
|
|
636
762
|
});
|
|
637
763
|
const heartbeat = async () => {
|
|
@@ -685,6 +811,7 @@ function createClivlySDK(config) {
|
|
|
685
811
|
}
|
|
686
812
|
};
|
|
687
813
|
const createClivlyHandler = () => async (req) => {
|
|
814
|
+
observedUrl = observedTriggerUrl(req) ?? observedUrl;
|
|
688
815
|
const rawBody = await req.text().catch(() => "");
|
|
689
816
|
if (!await triggerRequestAllowed(req, rawBody)) return jsonResponse({
|
|
690
817
|
ok: false,
|
|
@@ -64,6 +64,12 @@ interface ClivlySDKConfig {
|
|
|
64
64
|
* `contactEntity`.
|
|
65
65
|
*/
|
|
66
66
|
entities?: ClivlyEntitiesConfig;
|
|
67
|
+
/**
|
|
68
|
+
* Environment to read platform variables from when discovering the public
|
|
69
|
+
* trigger URL. Defaults to `process.env`. Injectable so tests and non-Node
|
|
70
|
+
* runtimes can supply their own.
|
|
71
|
+
*/
|
|
72
|
+
env?: Record<string, string | undefined>;
|
|
67
73
|
/**
|
|
68
74
|
* 🚧 Not implemented in 0.1.x. Reserved options for tunnel mode and CRM
|
|
69
75
|
* write-back — they have NO effect yet and land with the Cloudflare
|
|
@@ -140,8 +146,19 @@ interface ClivlySDKConfig {
|
|
|
140
146
|
* Public URL of the route where `createClivlyHandler()` is mounted, e.g.
|
|
141
147
|
* `https://app.example.com/api/clivly/tick`. Reported to Clivly on each
|
|
142
148
|
* heartbeat; a URL set in the dashboard overrides it.
|
|
149
|
+
*
|
|
150
|
+
* Optional — leave it unset and the SDK discovers the URL itself, from the
|
|
151
|
+
* hosting platform's environment or from the first request the route
|
|
152
|
+
* serves. Set it only to pin an address the SDK can't infer.
|
|
143
153
|
*/
|
|
144
154
|
url?: string;
|
|
155
|
+
/**
|
|
156
|
+
* Path where `createClivlyHandler()` is mounted, e.g. `/api/clivly/tick`.
|
|
157
|
+
* Written by `clivly init`. Combined with the origin detected from the
|
|
158
|
+
* hosting platform so the SDK can report a trigger URL on its very first
|
|
159
|
+
* heartbeat, before any request has arrived to observe.
|
|
160
|
+
*/
|
|
161
|
+
path?: string;
|
|
145
162
|
/**
|
|
146
163
|
* Signing secret (`whsec_…`) issued by Clivly under Integrations → Remote
|
|
147
164
|
* sync. When set, `createClivlyHandler()` rejects any request that isn't
|
|
@@ -64,6 +64,12 @@ interface ClivlySDKConfig {
|
|
|
64
64
|
* `contactEntity`.
|
|
65
65
|
*/
|
|
66
66
|
entities?: ClivlyEntitiesConfig;
|
|
67
|
+
/**
|
|
68
|
+
* Environment to read platform variables from when discovering the public
|
|
69
|
+
* trigger URL. Defaults to `process.env`. Injectable so tests and non-Node
|
|
70
|
+
* runtimes can supply their own.
|
|
71
|
+
*/
|
|
72
|
+
env?: Record<string, string | undefined>;
|
|
67
73
|
/**
|
|
68
74
|
* 🚧 Not implemented in 0.1.x. Reserved options for tunnel mode and CRM
|
|
69
75
|
* write-back — they have NO effect yet and land with the Cloudflare
|
|
@@ -140,8 +146,19 @@ interface ClivlySDKConfig {
|
|
|
140
146
|
* Public URL of the route where `createClivlyHandler()` is mounted, e.g.
|
|
141
147
|
* `https://app.example.com/api/clivly/tick`. Reported to Clivly on each
|
|
142
148
|
* heartbeat; a URL set in the dashboard overrides it.
|
|
149
|
+
*
|
|
150
|
+
* Optional — leave it unset and the SDK discovers the URL itself, from the
|
|
151
|
+
* hosting platform's environment or from the first request the route
|
|
152
|
+
* serves. Set it only to pin an address the SDK can't infer.
|
|
143
153
|
*/
|
|
144
154
|
url?: string;
|
|
155
|
+
/**
|
|
156
|
+
* Path where `createClivlyHandler()` is mounted, e.g. `/api/clivly/tick`.
|
|
157
|
+
* Written by `clivly init`. Combined with the origin detected from the
|
|
158
|
+
* hosting platform so the SDK can report a trigger URL on its very first
|
|
159
|
+
* heartbeat, before any request has arrived to observe.
|
|
160
|
+
*/
|
|
161
|
+
path?: string;
|
|
145
162
|
/**
|
|
146
163
|
* Signing secret (`whsec_…`) issued by Clivly under Integrations → Remote
|
|
147
164
|
* sync. When set, `createClivlyHandler()` rejects any request that isn't
|
package/dist/vite.d.cts
CHANGED
package/dist/vite.d.mts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clivly/sdk",
|
|
3
|
-
"version": "0.3.0-next.
|
|
3
|
+
"version": "0.3.0-next.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Clivly SDK — connect your app to Clivly Cloud (heartbeat, discovery, auth bridge).",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@clivly/core": "0.3.0-next.
|
|
48
|
+
"@clivly/core": "0.3.0-next.4"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"drizzle-orm": ">=0.30",
|