clivly 0.3.0-next.4 → 0.3.0-next.5
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/index.cjs +103 -7
- package/index.mjs +103 -7
- package/package.json +1 -1
- package/{sdk-DtFmeuwl.mjs → sdk-BLgY8K0U.mjs} +45 -2
- package/{sdk-r4khXzoH.cjs → sdk-Cc51uMdl.cjs} +45 -2
- package/sdk.cjs +1 -1
- package/sdk.mjs +1 -1
package/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const require_entity_config = require("./entity-config-DMuZpchz.cjs");
|
|
4
4
|
require("./mapping-score-i3p8LWDF.cjs");
|
|
5
5
|
const require_core_entity_heuristics = require("./core-entity-heuristics.cjs");
|
|
6
|
-
const require_sdk = require("./sdk-
|
|
6
|
+
const require_sdk = require("./sdk-Cc51uMdl.cjs");
|
|
7
7
|
let node_child_process = require("node:child_process");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
let node_module = require("node:module");
|
|
@@ -533,6 +533,71 @@ function isEntrypoint(argv1, moduleUrl, realpath = node_fs.realpathSync) {
|
|
|
533
533
|
return resolve(argv1) === resolve(modulePath);
|
|
534
534
|
}
|
|
535
535
|
//#endregion
|
|
536
|
+
//#region src/load-env.ts
|
|
537
|
+
const LINE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/;
|
|
538
|
+
const NEWLINE = /\r?\n/;
|
|
539
|
+
const ESCAPED_NEWLINE = /\\n/g;
|
|
540
|
+
const ESCAPED_CARRIAGE = /\\r/g;
|
|
541
|
+
const TRAILING_COMMENT = /\s#/;
|
|
542
|
+
/**
|
|
543
|
+
* Parse `.env` file text into a flat key→value map. Understands `#` comments
|
|
544
|
+
* (whole-line and trailing on unquoted values), single/double quotes,
|
|
545
|
+
* `export KEY=`, and `\n` escapes inside double quotes. Later duplicate keys
|
|
546
|
+
* win, matching how a shell would source the file top-to-bottom.
|
|
547
|
+
*/
|
|
548
|
+
function parseEnv(content) {
|
|
549
|
+
const out = {};
|
|
550
|
+
for (const rawLine of content.split(NEWLINE)) {
|
|
551
|
+
const line = rawLine.trimStart();
|
|
552
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
553
|
+
const match = LINE.exec(line);
|
|
554
|
+
const key = match?.[1];
|
|
555
|
+
if (!key) continue;
|
|
556
|
+
out[key] = parseValue(match[2] ?? "");
|
|
557
|
+
}
|
|
558
|
+
return out;
|
|
559
|
+
}
|
|
560
|
+
function parseValue(raw) {
|
|
561
|
+
const value = raw.trim();
|
|
562
|
+
if (value === "") return "";
|
|
563
|
+
const quote = value[0];
|
|
564
|
+
if (quote === "\"" || quote === "'") {
|
|
565
|
+
const end = value.indexOf(quote, 1);
|
|
566
|
+
const inner = end === -1 ? value.slice(1) : value.slice(1, end);
|
|
567
|
+
return quote === "\"" ? inner.replace(ESCAPED_NEWLINE, "\n").replace(ESCAPED_CARRIAGE, "\r") : inner;
|
|
568
|
+
}
|
|
569
|
+
const commentAt = value.search(TRAILING_COMMENT);
|
|
570
|
+
return (commentAt === -1 ? value : value.slice(0, commentAt)).trim();
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Load `.env` then `.env.local` from `cwd` into `env` (default `process.env`),
|
|
574
|
+
* WITHOUT overriding anything already set. Precedence, strongest first:
|
|
575
|
+
* real environment (shell) > `.env.local` > `.env`. This mirrors the framework
|
|
576
|
+
* convention developers already expect (Next.js, Vite), so pointing
|
|
577
|
+
* `CLIVLY_API_URL` at a staging host from the shell still wins over the file.
|
|
578
|
+
*/
|
|
579
|
+
function loadEnv(cwd, env = process.env) {
|
|
580
|
+
const files = [];
|
|
581
|
+
const merged = {};
|
|
582
|
+
for (const name of [".env", ".env.local"]) {
|
|
583
|
+
const path = (0, node_path.join)(cwd, name);
|
|
584
|
+
if (!(0, node_fs.existsSync)(path)) continue;
|
|
585
|
+
files.push(name);
|
|
586
|
+
try {
|
|
587
|
+
Object.assign(merged, parseEnv((0, node_fs.readFileSync)(path, "utf8")));
|
|
588
|
+
} catch {}
|
|
589
|
+
}
|
|
590
|
+
const applied = [];
|
|
591
|
+
for (const [key, value] of Object.entries(merged)) if (env[key] === void 0) {
|
|
592
|
+
env[key] = value;
|
|
593
|
+
applied.push(key);
|
|
594
|
+
}
|
|
595
|
+
return {
|
|
596
|
+
applied,
|
|
597
|
+
files
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
//#endregion
|
|
536
601
|
//#region src/login.ts
|
|
537
602
|
const POLL_INTERVAL_MS = 2e3;
|
|
538
603
|
const POLL_BACKOFF_MULTIPLIER = 1.5;
|
|
@@ -752,15 +817,25 @@ function placeholderContact(todos) {
|
|
|
752
817
|
idField: null
|
|
753
818
|
};
|
|
754
819
|
}
|
|
820
|
+
function hasMappedFields(entity) {
|
|
821
|
+
return Object.keys(entity.fields).length > 0;
|
|
822
|
+
}
|
|
755
823
|
function planInit(schema) {
|
|
756
824
|
const todos = [];
|
|
757
825
|
const { contact: contactTable, company: companyTable } = selectEntities(schema.tables);
|
|
758
|
-
|
|
759
|
-
|
|
826
|
+
let contact = contactTable ? buildEntity(contactTable, "contact", todos) : placeholderContact(todos);
|
|
827
|
+
let contactDetected = contactTable !== null;
|
|
828
|
+
if (!hasMappedFields(contact)) {
|
|
829
|
+
contact = placeholderContact(todos);
|
|
830
|
+
contactDetected = false;
|
|
831
|
+
}
|
|
760
832
|
let company = null;
|
|
761
833
|
if (companyTable) {
|
|
762
|
-
|
|
763
|
-
if (
|
|
834
|
+
const candidate = buildEntity(companyTable, "company", todos);
|
|
835
|
+
if (hasMappedFields(candidate)) {
|
|
836
|
+
company = candidate;
|
|
837
|
+
if (require_core_entity_heuristics.matchesConcept(companyTable.tableName, "contact")) todos.push(`"${companyTable.tableName}" matches both contact and company naming — assigned to company. Swap it to your contact entity if that's wrong.`);
|
|
838
|
+
} else todos.push(`"${companyTable.tableName}" looked like a company but no fields could be mapped — left it out. Add it to \`entities\` once you know which columns map to name/domain.`);
|
|
764
839
|
}
|
|
765
840
|
if (contactTable) {
|
|
766
841
|
const scored = require_core_entity_heuristics.scoreRelationships(toScoredTable(contactTable), schema.tables.map(toScoredTable));
|
|
@@ -1165,13 +1240,33 @@ function resolveConfigPath(cwd, configPath) {
|
|
|
1165
1240
|
function firstLine(message) {
|
|
1166
1241
|
return message.split("\n")[0]?.trim() ?? "";
|
|
1167
1242
|
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Describe why the config failed to load, in one line the developer can act on.
|
|
1245
|
+
*
|
|
1246
|
+
* A config that fails its own schema validation throws a zod error carrying
|
|
1247
|
+
* structured `issues`, but whose `message` is a multi-line JSON dump — its
|
|
1248
|
+
* first line is a bare "[", so `firstLine` alone reduced the entire diagnosis
|
|
1249
|
+
* to a single bracket. Render the issues as `path: message` instead, which is
|
|
1250
|
+
* what actually names the entity to fix. Anything else keeps the require-stack
|
|
1251
|
+
* trimming `firstLine` exists for.
|
|
1252
|
+
*/
|
|
1253
|
+
function describeLoadError(error) {
|
|
1254
|
+
const issues = error.issues;
|
|
1255
|
+
if (Array.isArray(issues) && issues.length > 0) return issues.map((raw) => {
|
|
1256
|
+
const issue = raw;
|
|
1257
|
+
const path = Array.isArray(issue.path) ? issue.path.join(".") : "";
|
|
1258
|
+
const message = typeof issue.message === "string" ? issue.message : "invalid value";
|
|
1259
|
+
return path ? `${path}: ${message}` : message;
|
|
1260
|
+
}).join("; ");
|
|
1261
|
+
return firstLine(error?.message ?? String(error));
|
|
1262
|
+
}
|
|
1168
1263
|
async function loadConfigSdk(path, requiredMethod) {
|
|
1169
1264
|
const jiti$1 = (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href);
|
|
1170
1265
|
let mod;
|
|
1171
1266
|
try {
|
|
1172
1267
|
mod = await jiti$1.import(path);
|
|
1173
1268
|
} catch (error) {
|
|
1174
|
-
throw new Error(
|
|
1269
|
+
throw new Error(describeLoadError(error));
|
|
1175
1270
|
}
|
|
1176
1271
|
const sdk = mod.default ?? mod;
|
|
1177
1272
|
if (typeof sdk?.[requiredMethod] !== "function") throw new Error("default export is not a Clivly SDK instance");
|
|
@@ -1654,7 +1749,7 @@ function previewMutations(deps, mutations) {
|
|
|
1654
1749
|
}
|
|
1655
1750
|
function printNextSteps(out, authComplete, loginRan) {
|
|
1656
1751
|
out("\nNext steps:\n");
|
|
1657
|
-
out(loginRan ? " 1. Credentials written by `clivly login`\n" : " 1.
|
|
1752
|
+
out(loginRan ? " 1. Credentials written by `clivly login`\n" : " 1. Add CLIVLY_API_KEY to .env — create one in the Clivly dashboard\n under Integrations → Create API Key (or Settings → API keys)\n");
|
|
1658
1753
|
out(" 2. Set CLIVLY_SYNC_TRIGGER_URL to your public tick route\n");
|
|
1659
1754
|
out(" Local dev? Run `clivly dev` to open a tunnel, or skip this step until you deploy (remote sync is deploy-only locally).\n");
|
|
1660
1755
|
const step = authComplete ? 3 : 4;
|
|
@@ -1872,6 +1967,7 @@ function runInitFromArgv(argv, cwd) {
|
|
|
1872
1967
|
}
|
|
1873
1968
|
async function main(argv) {
|
|
1874
1969
|
const [command, ...rest] = argv;
|
|
1970
|
+
loadEnv(process.cwd());
|
|
1875
1971
|
switch (command) {
|
|
1876
1972
|
case "init": return await runInitFromArgv(rest, process.cwd());
|
|
1877
1973
|
case "push-schema": return await runPushSchema({
|
package/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { t as CANONICAL_FIELDS } from "./entity-config-D3YpQB0z.mjs";
|
|
3
3
|
import "./mapping-score-CPPvwoZC.mjs";
|
|
4
4
|
import { matchesConcept, requiredFieldsFor, scoreFieldMap, scoreRelationships, suggestCursorField, toFieldMap } from "./core-entity-heuristics.mjs";
|
|
5
|
-
import { t as createClivlySDK } from "./sdk-
|
|
5
|
+
import { t as createClivlySDK } from "./sdk-BLgY8K0U.mjs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { accessSync, constants, existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -532,6 +532,71 @@ function isEntrypoint(argv1, moduleUrl, realpath = realpathSync) {
|
|
|
532
532
|
return resolve(argv1) === resolve(modulePath);
|
|
533
533
|
}
|
|
534
534
|
//#endregion
|
|
535
|
+
//#region src/load-env.ts
|
|
536
|
+
const LINE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/;
|
|
537
|
+
const NEWLINE = /\r?\n/;
|
|
538
|
+
const ESCAPED_NEWLINE = /\\n/g;
|
|
539
|
+
const ESCAPED_CARRIAGE = /\\r/g;
|
|
540
|
+
const TRAILING_COMMENT = /\s#/;
|
|
541
|
+
/**
|
|
542
|
+
* Parse `.env` file text into a flat key→value map. Understands `#` comments
|
|
543
|
+
* (whole-line and trailing on unquoted values), single/double quotes,
|
|
544
|
+
* `export KEY=`, and `\n` escapes inside double quotes. Later duplicate keys
|
|
545
|
+
* win, matching how a shell would source the file top-to-bottom.
|
|
546
|
+
*/
|
|
547
|
+
function parseEnv(content) {
|
|
548
|
+
const out = {};
|
|
549
|
+
for (const rawLine of content.split(NEWLINE)) {
|
|
550
|
+
const line = rawLine.trimStart();
|
|
551
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
552
|
+
const match = LINE.exec(line);
|
|
553
|
+
const key = match?.[1];
|
|
554
|
+
if (!key) continue;
|
|
555
|
+
out[key] = parseValue(match[2] ?? "");
|
|
556
|
+
}
|
|
557
|
+
return out;
|
|
558
|
+
}
|
|
559
|
+
function parseValue(raw) {
|
|
560
|
+
const value = raw.trim();
|
|
561
|
+
if (value === "") return "";
|
|
562
|
+
const quote = value[0];
|
|
563
|
+
if (quote === "\"" || quote === "'") {
|
|
564
|
+
const end = value.indexOf(quote, 1);
|
|
565
|
+
const inner = end === -1 ? value.slice(1) : value.slice(1, end);
|
|
566
|
+
return quote === "\"" ? inner.replace(ESCAPED_NEWLINE, "\n").replace(ESCAPED_CARRIAGE, "\r") : inner;
|
|
567
|
+
}
|
|
568
|
+
const commentAt = value.search(TRAILING_COMMENT);
|
|
569
|
+
return (commentAt === -1 ? value : value.slice(0, commentAt)).trim();
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Load `.env` then `.env.local` from `cwd` into `env` (default `process.env`),
|
|
573
|
+
* WITHOUT overriding anything already set. Precedence, strongest first:
|
|
574
|
+
* real environment (shell) > `.env.local` > `.env`. This mirrors the framework
|
|
575
|
+
* convention developers already expect (Next.js, Vite), so pointing
|
|
576
|
+
* `CLIVLY_API_URL` at a staging host from the shell still wins over the file.
|
|
577
|
+
*/
|
|
578
|
+
function loadEnv(cwd, env = process.env) {
|
|
579
|
+
const files = [];
|
|
580
|
+
const merged = {};
|
|
581
|
+
for (const name of [".env", ".env.local"]) {
|
|
582
|
+
const path = join(cwd, name);
|
|
583
|
+
if (!existsSync(path)) continue;
|
|
584
|
+
files.push(name);
|
|
585
|
+
try {
|
|
586
|
+
Object.assign(merged, parseEnv(readFileSync(path, "utf8")));
|
|
587
|
+
} catch {}
|
|
588
|
+
}
|
|
589
|
+
const applied = [];
|
|
590
|
+
for (const [key, value] of Object.entries(merged)) if (env[key] === void 0) {
|
|
591
|
+
env[key] = value;
|
|
592
|
+
applied.push(key);
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
applied,
|
|
596
|
+
files
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
//#endregion
|
|
535
600
|
//#region src/login.ts
|
|
536
601
|
const POLL_INTERVAL_MS = 2e3;
|
|
537
602
|
const POLL_BACKOFF_MULTIPLIER = 1.5;
|
|
@@ -751,15 +816,25 @@ function placeholderContact(todos) {
|
|
|
751
816
|
idField: null
|
|
752
817
|
};
|
|
753
818
|
}
|
|
819
|
+
function hasMappedFields(entity) {
|
|
820
|
+
return Object.keys(entity.fields).length > 0;
|
|
821
|
+
}
|
|
754
822
|
function planInit(schema) {
|
|
755
823
|
const todos = [];
|
|
756
824
|
const { contact: contactTable, company: companyTable } = selectEntities(schema.tables);
|
|
757
|
-
|
|
758
|
-
|
|
825
|
+
let contact = contactTable ? buildEntity(contactTable, "contact", todos) : placeholderContact(todos);
|
|
826
|
+
let contactDetected = contactTable !== null;
|
|
827
|
+
if (!hasMappedFields(contact)) {
|
|
828
|
+
contact = placeholderContact(todos);
|
|
829
|
+
contactDetected = false;
|
|
830
|
+
}
|
|
759
831
|
let company = null;
|
|
760
832
|
if (companyTable) {
|
|
761
|
-
|
|
762
|
-
if (
|
|
833
|
+
const candidate = buildEntity(companyTable, "company", todos);
|
|
834
|
+
if (hasMappedFields(candidate)) {
|
|
835
|
+
company = candidate;
|
|
836
|
+
if (matchesConcept(companyTable.tableName, "contact")) todos.push(`"${companyTable.tableName}" matches both contact and company naming — assigned to company. Swap it to your contact entity if that's wrong.`);
|
|
837
|
+
} else todos.push(`"${companyTable.tableName}" looked like a company but no fields could be mapped — left it out. Add it to \`entities\` once you know which columns map to name/domain.`);
|
|
763
838
|
}
|
|
764
839
|
if (contactTable) {
|
|
765
840
|
const scored = scoreRelationships(toScoredTable(contactTable), schema.tables.map(toScoredTable));
|
|
@@ -1164,13 +1239,33 @@ function resolveConfigPath(cwd, configPath) {
|
|
|
1164
1239
|
function firstLine(message) {
|
|
1165
1240
|
return message.split("\n")[0]?.trim() ?? "";
|
|
1166
1241
|
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Describe why the config failed to load, in one line the developer can act on.
|
|
1244
|
+
*
|
|
1245
|
+
* A config that fails its own schema validation throws a zod error carrying
|
|
1246
|
+
* structured `issues`, but whose `message` is a multi-line JSON dump — its
|
|
1247
|
+
* first line is a bare "[", so `firstLine` alone reduced the entire diagnosis
|
|
1248
|
+
* to a single bracket. Render the issues as `path: message` instead, which is
|
|
1249
|
+
* what actually names the entity to fix. Anything else keeps the require-stack
|
|
1250
|
+
* trimming `firstLine` exists for.
|
|
1251
|
+
*/
|
|
1252
|
+
function describeLoadError(error) {
|
|
1253
|
+
const issues = error.issues;
|
|
1254
|
+
if (Array.isArray(issues) && issues.length > 0) return issues.map((raw) => {
|
|
1255
|
+
const issue = raw;
|
|
1256
|
+
const path = Array.isArray(issue.path) ? issue.path.join(".") : "";
|
|
1257
|
+
const message = typeof issue.message === "string" ? issue.message : "invalid value";
|
|
1258
|
+
return path ? `${path}: ${message}` : message;
|
|
1259
|
+
}).join("; ");
|
|
1260
|
+
return firstLine(error?.message ?? String(error));
|
|
1261
|
+
}
|
|
1167
1262
|
async function loadConfigSdk(path, requiredMethod) {
|
|
1168
1263
|
const jiti = createJiti(import.meta.url);
|
|
1169
1264
|
let mod;
|
|
1170
1265
|
try {
|
|
1171
1266
|
mod = await jiti.import(path);
|
|
1172
1267
|
} catch (error) {
|
|
1173
|
-
throw new Error(
|
|
1268
|
+
throw new Error(describeLoadError(error));
|
|
1174
1269
|
}
|
|
1175
1270
|
const sdk = mod.default ?? mod;
|
|
1176
1271
|
if (typeof sdk?.[requiredMethod] !== "function") throw new Error("default export is not a Clivly SDK instance");
|
|
@@ -1653,7 +1748,7 @@ function previewMutations(deps, mutations) {
|
|
|
1653
1748
|
}
|
|
1654
1749
|
function printNextSteps(out, authComplete, loginRan) {
|
|
1655
1750
|
out("\nNext steps:\n");
|
|
1656
|
-
out(loginRan ? " 1. Credentials written by `clivly login`\n" : " 1.
|
|
1751
|
+
out(loginRan ? " 1. Credentials written by `clivly login`\n" : " 1. Add CLIVLY_API_KEY to .env — create one in the Clivly dashboard\n under Integrations → Create API Key (or Settings → API keys)\n");
|
|
1657
1752
|
out(" 2. Set CLIVLY_SYNC_TRIGGER_URL to your public tick route\n");
|
|
1658
1753
|
out(" Local dev? Run `clivly dev` to open a tunnel, or skip this step until you deploy (remote sync is deploy-only locally).\n");
|
|
1659
1754
|
const step = authComplete ? 3 : 4;
|
|
@@ -1871,6 +1966,7 @@ function runInitFromArgv(argv, cwd) {
|
|
|
1871
1966
|
}
|
|
1872
1967
|
async function main(argv) {
|
|
1873
1968
|
const [command, ...rest] = argv;
|
|
1969
|
+
loadEnv(process.cwd());
|
|
1874
1970
|
switch (command) {
|
|
1875
1971
|
case "init": return await runInitFromArgv(rest, process.cwd());
|
|
1876
1972
|
case "push-schema": return await runPushSchema({
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ function checkCustomObjectSlugs(config) {
|
|
|
11
11
|
name: "Custom object slugs",
|
|
12
12
|
ok: false,
|
|
13
13
|
skipped: true,
|
|
14
|
-
detail: "No custom objects
|
|
14
|
+
detail: "No custom objects in clivly.config. Objects added in the dashboard need a matching source.custom entry here to sync."
|
|
15
15
|
};
|
|
16
16
|
const problems = [];
|
|
17
17
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -36,6 +36,49 @@ function checkCustomObjectSlugs(config) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
//#endregion
|
|
39
|
+
//#region ../sdk/src/fetch-error.ts
|
|
40
|
+
/** Deepest `Error` reachable through the `.cause` chain (the original error if none). */
|
|
41
|
+
function rootError(error) {
|
|
42
|
+
let current = error;
|
|
43
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44
|
+
while (current instanceof Error && current.cause !== void 0 && !seen.has(current)) {
|
|
45
|
+
seen.add(current);
|
|
46
|
+
current = current.cause;
|
|
47
|
+
}
|
|
48
|
+
return current;
|
|
49
|
+
}
|
|
50
|
+
function codeOf(error) {
|
|
51
|
+
const code = error?.code;
|
|
52
|
+
return typeof code === "string" ? code : void 0;
|
|
53
|
+
}
|
|
54
|
+
const CODE_HINTS = {
|
|
55
|
+
ECONNREFUSED: "the database refused the connection — is Postgres running and is your database URL (e.g. DATABASE_URL) pointing at it?",
|
|
56
|
+
ENOTFOUND: "the database host couldn't be resolved — check the host in your connection string.",
|
|
57
|
+
ETIMEDOUT: "the database connection timed out — check the host, port, and firewall.",
|
|
58
|
+
"28P01": "password authentication failed — check the credentials in your connection string.",
|
|
59
|
+
"28000": "the database rejected the connection role — check the user in your connection string.",
|
|
60
|
+
"3D000": "that database does not exist — check the database name in your connection string.",
|
|
61
|
+
"08006": "the database connection was lost — check that Postgres is reachable."
|
|
62
|
+
};
|
|
63
|
+
const NEWLINE = /\r?\n/;
|
|
64
|
+
/** One-line, single-spaced version of an error message (drops the SQL/params blob). */
|
|
65
|
+
function firstLine(message) {
|
|
66
|
+
const line = message.split(NEWLINE)[0]?.trim() ?? "";
|
|
67
|
+
return line === "" ? message.trim() : line;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Build the `detail` string for a failed sample fetch of `entity`.
|
|
71
|
+
* Recognised connection errors get a plain-English hint; everything else
|
|
72
|
+
* surfaces the root cause's first line rather than the wrapper's SQL dump.
|
|
73
|
+
*/
|
|
74
|
+
function describeFetchError(entity, error) {
|
|
75
|
+
const root = rootError(error);
|
|
76
|
+
const code = codeOf(root) ?? codeOf(error);
|
|
77
|
+
const hint = code ? CODE_HINTS[code] : void 0;
|
|
78
|
+
if (hint) return `Couldn't read "${entity}": ${hint}`;
|
|
79
|
+
return `Couldn't read "${entity}": ${firstLine(root instanceof Error ? root.message : String(root?.message ?? root ?? "unknown error"))}`;
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
39
82
|
//#region ../sdk/src/namespace-probe.ts
|
|
40
83
|
/**
|
|
41
84
|
* Verify the tables the integration depends on are genuinely reachable:
|
|
@@ -187,7 +230,7 @@ async function runDoctor(args) {
|
|
|
187
230
|
checks.push({
|
|
188
231
|
name: `Sample fetch (${source.entity})`,
|
|
189
232
|
ok: false,
|
|
190
|
-
detail:
|
|
233
|
+
detail: describeFetchError(source.entity, error)
|
|
191
234
|
});
|
|
192
235
|
}
|
|
193
236
|
const result = await connect();
|
|
@@ -11,7 +11,7 @@ function checkCustomObjectSlugs(config) {
|
|
|
11
11
|
name: "Custom object slugs",
|
|
12
12
|
ok: false,
|
|
13
13
|
skipped: true,
|
|
14
|
-
detail: "No custom objects
|
|
14
|
+
detail: "No custom objects in clivly.config. Objects added in the dashboard need a matching source.custom entry here to sync."
|
|
15
15
|
};
|
|
16
16
|
const problems = [];
|
|
17
17
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -36,6 +36,49 @@ function checkCustomObjectSlugs(config) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
//#endregion
|
|
39
|
+
//#region ../sdk/src/fetch-error.ts
|
|
40
|
+
/** Deepest `Error` reachable through the `.cause` chain (the original error if none). */
|
|
41
|
+
function rootError(error) {
|
|
42
|
+
let current = error;
|
|
43
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44
|
+
while (current instanceof Error && current.cause !== void 0 && !seen.has(current)) {
|
|
45
|
+
seen.add(current);
|
|
46
|
+
current = current.cause;
|
|
47
|
+
}
|
|
48
|
+
return current;
|
|
49
|
+
}
|
|
50
|
+
function codeOf(error) {
|
|
51
|
+
const code = error?.code;
|
|
52
|
+
return typeof code === "string" ? code : void 0;
|
|
53
|
+
}
|
|
54
|
+
const CODE_HINTS = {
|
|
55
|
+
ECONNREFUSED: "the database refused the connection — is Postgres running and is your database URL (e.g. DATABASE_URL) pointing at it?",
|
|
56
|
+
ENOTFOUND: "the database host couldn't be resolved — check the host in your connection string.",
|
|
57
|
+
ETIMEDOUT: "the database connection timed out — check the host, port, and firewall.",
|
|
58
|
+
"28P01": "password authentication failed — check the credentials in your connection string.",
|
|
59
|
+
"28000": "the database rejected the connection role — check the user in your connection string.",
|
|
60
|
+
"3D000": "that database does not exist — check the database name in your connection string.",
|
|
61
|
+
"08006": "the database connection was lost — check that Postgres is reachable."
|
|
62
|
+
};
|
|
63
|
+
const NEWLINE = /\r?\n/;
|
|
64
|
+
/** One-line, single-spaced version of an error message (drops the SQL/params blob). */
|
|
65
|
+
function firstLine(message) {
|
|
66
|
+
const line = message.split(NEWLINE)[0]?.trim() ?? "";
|
|
67
|
+
return line === "" ? message.trim() : line;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Build the `detail` string for a failed sample fetch of `entity`.
|
|
71
|
+
* Recognised connection errors get a plain-English hint; everything else
|
|
72
|
+
* surfaces the root cause's first line rather than the wrapper's SQL dump.
|
|
73
|
+
*/
|
|
74
|
+
function describeFetchError(entity, error) {
|
|
75
|
+
const root = rootError(error);
|
|
76
|
+
const code = codeOf(root) ?? codeOf(error);
|
|
77
|
+
const hint = code ? CODE_HINTS[code] : void 0;
|
|
78
|
+
if (hint) return `Couldn't read "${entity}": ${hint}`;
|
|
79
|
+
return `Couldn't read "${entity}": ${firstLine(root instanceof Error ? root.message : String(root?.message ?? root ?? "unknown error"))}`;
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
39
82
|
//#region ../sdk/src/namespace-probe.ts
|
|
40
83
|
/**
|
|
41
84
|
* Verify the tables the integration depends on are genuinely reachable:
|
|
@@ -187,7 +230,7 @@ async function runDoctor(args) {
|
|
|
187
230
|
checks.push({
|
|
188
231
|
name: `Sample fetch (${source.entity})`,
|
|
189
232
|
ok: false,
|
|
190
|
-
detail:
|
|
233
|
+
detail: describeFetchError(source.entity, error)
|
|
191
234
|
});
|
|
192
235
|
}
|
|
193
236
|
const result = await connect();
|
package/sdk.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_sdk = require("./sdk-
|
|
2
|
+
const require_sdk = require("./sdk-Cc51uMdl.cjs");
|
|
3
3
|
exports.ClivlyAuthError = require_sdk.ClivlyAuthError;
|
|
4
4
|
exports.ClivlyError = require_sdk.ClivlyError;
|
|
5
5
|
exports.ClivlyNetworkError = require_sdk.ClivlyNetworkError;
|
package/sdk.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as ClivlyRateLimitError, i as ClivlyNetworkError, n as ClivlyAuthError, r as ClivlyError, t as createClivlySDK } from "./sdk-
|
|
1
|
+
import { a as ClivlyRateLimitError, i as ClivlyNetworkError, n as ClivlyAuthError, r as ClivlyError, t as createClivlySDK } from "./sdk-BLgY8K0U.mjs";
|
|
2
2
|
export { ClivlyAuthError, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, createClivlySDK };
|