clivly 0.10.6 → 0.10.8
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/core-entity-heuristics.cjs +1 -0
- package/core-entity-heuristics.d.cts +2 -2
- package/core-entity-heuristics.d.mts +2 -2
- package/core-entity-heuristics.mjs +2 -2
- package/drizzle.cjs +2 -0
- package/drizzle.d.cts +1 -1
- package/drizzle.d.mts +1 -1
- package/drizzle.mjs +2 -0
- package/index.cjs +91 -12
- package/index.mjs +93 -14
- package/internal-entity-heuristics.cjs +49 -0
- package/internal-entity-heuristics.d.cts +9 -1
- package/internal-entity-heuristics.d.mts +9 -1
- package/internal-entity-heuristics.mjs +49 -1
- package/{namespace-probe-ouHKZas2.d.mts → namespace-probe-BmxtnOfc.d.mts} +16 -0
- package/{namespace-probe-lj_0bJrc.d.cts → namespace-probe-C3xMOqWE.d.cts} +16 -0
- package/package.json +1 -1
- package/{sdk-DbvqXNSh.cjs → sdk-BAHcnhm7.cjs} +44 -4
- package/{sdk-BTzaM8bF.mjs → sdk-C0DU5KDS.mjs} +44 -4
- package/sdk.cjs +1 -1
- package/sdk.d.cts +1 -1
- package/sdk.d.mts +1 -1
- package/sdk.mjs +1 -1
- package/vite.d.cts +1 -1
- package/vite.d.mts +1 -1
|
@@ -6,6 +6,7 @@ require("./deprecate-subpath-B9ngvizt.cjs").warnDeprecatedSubpath("clivly/core/e
|
|
|
6
6
|
exports.TARGET_CONCEPTS = require_internal_entity_heuristics.TARGET_CONCEPTS;
|
|
7
7
|
exports.hasDefiningColumn = require_internal_entity_heuristics.hasDefiningColumn;
|
|
8
8
|
exports.hasSensitiveColumns = require_internal_entity_heuristics.hasSensitiveColumns;
|
|
9
|
+
exports.isFrameworkTable = require_internal_entity_heuristics.isFrameworkTable;
|
|
9
10
|
exports.matchesConcept = require_internal_entity_heuristics.matchesConcept;
|
|
10
11
|
exports.requiredFieldsFor = require_internal_entity_heuristics.requiredFieldsFor;
|
|
11
12
|
exports.scoreConcepts = require_internal_entity_heuristics.scoreConcepts;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { p as DiscoveredColumnMeta } from "./entity-config-w8oMBWf8.cjs";
|
|
2
|
-
import { FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping } from "./internal-entity-heuristics.cjs";
|
|
3
|
-
export { type DiscoveredColumnMeta, FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
2
|
+
import { FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, isFrameworkTable, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping } from "./internal-entity-heuristics.cjs";
|
|
3
|
+
export { type DiscoveredColumnMeta, FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, isFrameworkTable, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { p as DiscoveredColumnMeta } from "./entity-config-w8oMBWf8.mjs";
|
|
2
|
-
import { FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping } from "./internal-entity-heuristics.mjs";
|
|
3
|
-
export { type DiscoveredColumnMeta, FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
2
|
+
import { FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, isFrameworkTable, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping } from "./internal-entity-heuristics.mjs";
|
|
3
|
+
export { type DiscoveredColumnMeta, FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, isFrameworkTable, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { TARGET_CONCEPTS, hasDefiningColumn, hasSensitiveColumns, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping } from "./internal-entity-heuristics.mjs";
|
|
1
|
+
import { TARGET_CONCEPTS, hasDefiningColumn, hasSensitiveColumns, isFrameworkTable, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping } from "./internal-entity-heuristics.mjs";
|
|
2
2
|
import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
|
|
3
3
|
//#region src/core-entity-heuristics.ts
|
|
4
4
|
warnDeprecatedSubpath("clivly/core/entity-heuristics", "clivly/internal/entity-heuristics");
|
|
5
5
|
//#endregion
|
|
6
|
-
export { TARGET_CONCEPTS, hasDefiningColumn, hasSensitiveColumns, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
6
|
+
export { TARGET_CONCEPTS, hasDefiningColumn, hasSensitiveColumns, isFrameworkTable, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
package/drizzle.cjs
CHANGED
|
@@ -60,6 +60,7 @@ function fromDrizzleProjection(db, projection, opts) {
|
|
|
60
60
|
const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
|
|
61
61
|
return {
|
|
62
62
|
entity: opts.entity,
|
|
63
|
+
pushSource: projection.baseTable,
|
|
63
64
|
cursorField: cursorToken,
|
|
64
65
|
idField: "id",
|
|
65
66
|
...opts.objectType === void 0 ? {} : { objectType: opts.objectType },
|
|
@@ -89,6 +90,7 @@ function fromDrizzleProjectionByIdentity(client, projection, identityExpr, fromC
|
|
|
89
90
|
const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
|
|
90
91
|
return {
|
|
91
92
|
entity: opts.entity,
|
|
93
|
+
pushSource: projection.baseTable,
|
|
92
94
|
cursorField: "id",
|
|
93
95
|
idField: "id",
|
|
94
96
|
...opts.objectType === void 0 ? {} : { objectType: opts.objectType },
|
package/drizzle.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { R as ProjectionMapping, z as ProjectionRef } from "./entity-config-w8oMBWf8.cjs";
|
|
2
2
|
import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-SD8WCvYj.cjs";
|
|
3
|
-
import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-
|
|
3
|
+
import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-C3xMOqWE.cjs";
|
|
4
4
|
import { Table } from "drizzle-orm";
|
|
5
5
|
//#region ../sdk/src/drizzle-projection.d.ts
|
|
6
6
|
/**
|
package/drizzle.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { R as ProjectionMapping, z as ProjectionRef } from "./entity-config-w8oMBWf8.mjs";
|
|
2
2
|
import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-axugRJ0u.mjs";
|
|
3
|
-
import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-
|
|
3
|
+
import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-BmxtnOfc.mjs";
|
|
4
4
|
import { Table } from "drizzle-orm";
|
|
5
5
|
//#region ../sdk/src/drizzle-projection.d.ts
|
|
6
6
|
/**
|
package/drizzle.mjs
CHANGED
|
@@ -59,6 +59,7 @@ function fromDrizzleProjection(db, projection, opts) {
|
|
|
59
59
|
const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
|
|
60
60
|
return {
|
|
61
61
|
entity: opts.entity,
|
|
62
|
+
pushSource: projection.baseTable,
|
|
62
63
|
cursorField: cursorToken,
|
|
63
64
|
idField: "id",
|
|
64
65
|
...opts.objectType === void 0 ? {} : { objectType: opts.objectType },
|
|
@@ -88,6 +89,7 @@ function fromDrizzleProjectionByIdentity(client, projection, identityExpr, fromC
|
|
|
88
89
|
const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
|
|
89
90
|
return {
|
|
90
91
|
entity: opts.entity,
|
|
92
|
+
pushSource: projection.baseTable,
|
|
91
93
|
cursorField: "id",
|
|
92
94
|
idField: "id",
|
|
93
95
|
...opts.objectType === void 0 ? {} : { objectType: opts.objectType },
|
package/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const require_entity_config = require("./entity-config-C2MfIDL2.cjs");
|
|
4
4
|
require("./mapping-score-CaB3F4Uu.cjs");
|
|
5
5
|
const require_internal_entity_heuristics = require("./internal-entity-heuristics.cjs");
|
|
6
|
-
const require_sdk = require("./sdk-
|
|
6
|
+
const require_sdk = require("./sdk-BAHcnhm7.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");
|
|
@@ -1657,13 +1657,14 @@ function writeCredentials(options, credentials) {
|
|
|
1657
1657
|
const entries = credentialEntries(credentials);
|
|
1658
1658
|
const result = upsertEnvValues(deps.readEnv(options.cwd), entries, { force: options.force });
|
|
1659
1659
|
deps.writeEnv(options.cwd, result.contents);
|
|
1660
|
+
const envFile = deps.envFileName?.(options.cwd) ?? ".env";
|
|
1660
1661
|
deps.out("\nLogin successful.\n");
|
|
1661
1662
|
const valuesByKey = {
|
|
1662
1663
|
[SECRET_KEY_ENV]: credentials.apiKey,
|
|
1663
1664
|
CLIVLY_SYNC_TRIGGER_SECRET: credentials.triggerSecret
|
|
1664
1665
|
};
|
|
1665
1666
|
for (const entry of entries) if (result.skipped.includes(entry.key)) deps.out(` • ${entry.key} already set — skipped (pass --force to overwrite)`);
|
|
1666
|
-
else deps.out(` ✓ ${entry.key}=${maskSecret(valuesByKey[entry.key] ?? "")} written to
|
|
1667
|
+
else deps.out(` ✓ ${entry.key}=${maskSecret(valuesByKey[entry.key] ?? "")} written to ${envFile}`);
|
|
1667
1668
|
}
|
|
1668
1669
|
async function runLogin(options) {
|
|
1669
1670
|
const { deps } = options;
|
|
@@ -1706,7 +1707,10 @@ function rankCandidates$1(tables, concept) {
|
|
|
1706
1707
|
m: require_internal_entity_heuristics.matchesConcept(t.tableName, concept)
|
|
1707
1708
|
})).filter((c) => Boolean(c.m)).sort((a, b) => {
|
|
1708
1709
|
const usable = Number(require_internal_entity_heuristics.hasDefiningColumn(concept, columnNames(b.t))) - Number(require_internal_entity_heuristics.hasDefiningColumn(concept, columnNames(a.t)));
|
|
1709
|
-
|
|
1710
|
+
if (usable !== 0) return usable;
|
|
1711
|
+
const exact = Number(b.m.exact) - Number(a.m.exact);
|
|
1712
|
+
if (exact !== 0) return exact;
|
|
1713
|
+
return Number(require_internal_entity_heuristics.isFrameworkTable(a.t.tableName)) - Number(require_internal_entity_heuristics.isFrameworkTable(b.t.tableName));
|
|
1710
1714
|
}).map((c) => c.t);
|
|
1711
1715
|
}
|
|
1712
1716
|
function selectEntities(tables) {
|
|
@@ -1949,6 +1953,18 @@ export async function register() {
|
|
|
1949
1953
|
return;
|
|
1950
1954
|
}
|
|
1951
1955
|
try {
|
|
1956
|
+
// A plain import, deliberately. Next compiles this file for BOTH runtimes
|
|
1957
|
+
// and follows this into your config and on into your database driver, so a
|
|
1958
|
+
// Node-only driver breaks the EDGE build — the guard above is a runtime
|
|
1959
|
+
// check and cannot prevent that. The fix belongs in next.config, where
|
|
1960
|
+
// \`clivly init\` writes an edge-only \`config.externals\` entry; nothing here
|
|
1961
|
+
// ever executes on the edge, so the driver only has to resolve.
|
|
1962
|
+
//
|
|
1963
|
+
// Do NOT make this untraceable (a variable specifier, webpackIgnore).
|
|
1964
|
+
// That silences the build error and breaks the app in a worse way:
|
|
1965
|
+
// clivly.config is TypeScript, so an unbundled import cannot load it, and
|
|
1966
|
+
// the catch below turns that into a warning — the app boots, serves
|
|
1967
|
+
// traffic, and never checks in.
|
|
1952
1968
|
const { default: clivly } = await import("${importPath}");
|
|
1953
1969
|
await clivly.start();
|
|
1954
1970
|
} catch (error) {
|
|
@@ -2002,6 +2018,46 @@ function nextNeedsInstrumentationFlag(versionRange) {
|
|
|
2002
2018
|
}
|
|
2003
2019
|
const NEXT_INSTRUMENTATION_FLAG_STEP = `Your Next.js version reads instrumentation.ts only when it is switched on. Add this to next.config, or Clivly will never be told your app started:
|
|
2004
2020
|
experimental: { instrumentationHook: true }`;
|
|
2021
|
+
const NODE_ONLY_DRIVERS = /* @__PURE__ */ new Set([
|
|
2022
|
+
"pg",
|
|
2023
|
+
"mysql",
|
|
2024
|
+
"mysql2",
|
|
2025
|
+
"better-sqlite3",
|
|
2026
|
+
"sqlite3",
|
|
2027
|
+
"tedious",
|
|
2028
|
+
"oracledb",
|
|
2029
|
+
"ioredis"
|
|
2030
|
+
]);
|
|
2031
|
+
/**
|
|
2032
|
+
* The Node-only database drivers a config's dependency list contains.
|
|
2033
|
+
*
|
|
2034
|
+
* Next compiles instrumentation.ts for the edge runtime whether or not the app
|
|
2035
|
+
* uses it, and follows the boot hook's config import into the driver. `pg`
|
|
2036
|
+
* pulls in `pgpass` → `split2` → `stream`, none of which resolve there, so the
|
|
2037
|
+
* build fails and every route 500s — before any Clivly code runs.
|
|
2038
|
+
*
|
|
2039
|
+
* `serverExternalPackages` does NOT fix this. It applies to the Node server
|
|
2040
|
+
* bundle only; the edge pass ignores it entirely, which is why adding package
|
|
2041
|
+
* after package to it never terminates. An edge-only `config.externals` is what
|
|
2042
|
+
* works, and it is safe precisely because nothing in that bundle ever executes
|
|
2043
|
+
* — the boot hook returns early unless NEXT_RUNTIME is "nodejs".
|
|
2044
|
+
*/
|
|
2045
|
+
function nodeOnlyDrivers(dependencies) {
|
|
2046
|
+
if (!dependencies) return [];
|
|
2047
|
+
return Object.keys(dependencies).filter((name) => NODE_ONLY_DRIVERS.has(name)).sort();
|
|
2048
|
+
}
|
|
2049
|
+
/** The next.config step an app needs when its config reaches a Node driver. */
|
|
2050
|
+
function nextEdgeExternalsStep(drivers) {
|
|
2051
|
+
const list = drivers.map((d) => `"${d}"`).join(", ");
|
|
2052
|
+
return `Your database driver (${drivers.join(", ")}) cannot be bundled for Next's edge runtime, which compiles instrumentation.ts whether or not you use it. Without this, every route fails to build. Add to next.config:
|
|
2053
|
+
webpack: (config, { nextRuntime }) => {
|
|
2054
|
+
if (nextRuntime === "edge") {
|
|
2055
|
+
config.externals = [...(config.externals ?? []), ${list}];
|
|
2056
|
+
}
|
|
2057
|
+
return config;
|
|
2058
|
+
}
|
|
2059
|
+
Nothing runs on the edge — the boot hook exits early there — so the driver only has to resolve.`;
|
|
2060
|
+
}
|
|
2005
2061
|
const REMIX_BOOT_STEP = `Start Clivly when your app boots. Add this near the top of app/entry.server.tsx:
|
|
2006
2062
|
import clivly from "./clivly.config";
|
|
2007
2063
|
clivly.start().catch((error) => console.warn("[clivly] could not check in:", error));`;
|
|
@@ -2552,21 +2608,32 @@ function planEnv(args) {
|
|
|
2552
2608
|
action: envAction(addedKeys.length, existing.length)
|
|
2553
2609
|
};
|
|
2554
2610
|
}
|
|
2555
|
-
function
|
|
2611
|
+
function hostManifest(args) {
|
|
2556
2612
|
const raw = args.readFile((0, node_path.join)(args.cwd, "package.json"));
|
|
2557
|
-
if (!raw) return;
|
|
2558
|
-
let pkg = {};
|
|
2613
|
+
if (!raw) return {};
|
|
2559
2614
|
try {
|
|
2560
|
-
|
|
2615
|
+
return JSON.parse(raw);
|
|
2561
2616
|
} catch {
|
|
2562
|
-
|
|
2617
|
+
return {};
|
|
2563
2618
|
}
|
|
2619
|
+
}
|
|
2620
|
+
function declaredNextVersion(args) {
|
|
2621
|
+
const pkg = hostManifest(args);
|
|
2564
2622
|
return pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
2565
2623
|
}
|
|
2624
|
+
function declaredDependencies(args) {
|
|
2625
|
+
return hostManifest(args).dependencies;
|
|
2626
|
+
}
|
|
2627
|
+
/** The edge-externals step, or null when no Node-only driver is declared. */
|
|
2628
|
+
function edgeExternalsStepFor(dependencies) {
|
|
2629
|
+
const drivers = nodeOnlyDrivers(dependencies);
|
|
2630
|
+
return drivers.length > 0 ? nextEdgeExternalsStep(drivers) : null;
|
|
2631
|
+
}
|
|
2566
2632
|
function planBootPatch(args) {
|
|
2567
2633
|
if (bootHookPath(args.framework, args.layout) !== null) return {
|
|
2568
2634
|
file: null,
|
|
2569
|
-
manual: args.framework === "nextjs" && nextNeedsInstrumentationFlag(declaredNextVersion(args)) ? NEXT_INSTRUMENTATION_FLAG_STEP : null
|
|
2635
|
+
manual: args.framework === "nextjs" && nextNeedsInstrumentationFlag(declaredNextVersion(args)) ? NEXT_INSTRUMENTATION_FLAG_STEP : null,
|
|
2636
|
+
extra: args.framework === "nextjs" ? edgeExternalsStepFor(declaredDependencies(args)) : null
|
|
2570
2637
|
};
|
|
2571
2638
|
if (args.framework === "remix") return {
|
|
2572
2639
|
file: null,
|
|
@@ -2612,7 +2679,7 @@ function planMutations(args) {
|
|
|
2612
2679
|
return {
|
|
2613
2680
|
files,
|
|
2614
2681
|
env: planEnv(args),
|
|
2615
|
-
manualSteps: boot.manual
|
|
2682
|
+
manualSteps: [boot.manual, boot.extra ?? null].filter((step) => step !== null)
|
|
2616
2683
|
};
|
|
2617
2684
|
}
|
|
2618
2685
|
function planWidgetMutations(args) {
|
|
@@ -2757,7 +2824,14 @@ function reportSchema(report, out) {
|
|
|
2757
2824
|
const updated = report.mappings.filter((m) => m.updated);
|
|
2758
2825
|
if (created.length > 0) out(` ✅ Mapped ${countOf(created.length, "entity", "entities")}: ${created.map((m) => m.entityKey).join(", ")}.\n`);
|
|
2759
2826
|
if (updated.length > 0) out(` ✅ Updated ${countOf(updated.length, "mapping")}: ${updated.map((m) => m.entityKey).join(", ")}.\n`);
|
|
2760
|
-
|
|
2827
|
+
const skipped = report.mappings.filter((m) => m.skipped === "dashboard-owned");
|
|
2828
|
+
if (skipped.length > 0) {
|
|
2829
|
+
out(` ↩︎ Kept the dashboard's version of ${countOf(skipped.length, "entity", "entities")}: ${skipped.map((m) => m.entityKey).join(", ")}.\n`);
|
|
2830
|
+
out(" The dashboard owns these mappings, so clivly.config did not change them.\n");
|
|
2831
|
+
out(" To let your config own one, delete it in Integrations → Mapping, then push again.\n");
|
|
2832
|
+
}
|
|
2833
|
+
const settled = report.mappings.length - skipped.length;
|
|
2834
|
+
if (settled > 0 && created.length === 0 && updated.length === 0) out(` ✅ ${countOf(settled, "mapping")} already match your config.\n`);
|
|
2761
2835
|
}
|
|
2762
2836
|
async function runPushSchema(options) {
|
|
2763
2837
|
const out = options.out ?? ((line) => process.stdout.write(line));
|
|
@@ -4012,6 +4086,10 @@ function previewMutations(deps, mutations, options) {
|
|
|
4012
4086
|
out(` ${mutations.env.action.padEnd(9)} .env\n`);
|
|
4013
4087
|
for (const key of mutations.env.addedKeys) out(` + ${key}=\n`);
|
|
4014
4088
|
}
|
|
4089
|
+
if (mutations.manualSteps.length > 0) {
|
|
4090
|
+
out("\nYou will also need to:\n");
|
|
4091
|
+
for (const step of mutations.manualSteps) out(`\n ! ${step}\n`);
|
|
4092
|
+
}
|
|
4015
4093
|
out(options.diff ? "\nRe-run without --diff to apply; --force to overwrite the files marked conflict.\n" : "\nRe-run without --dry-run to apply.\n");
|
|
4016
4094
|
}
|
|
4017
4095
|
function printNextSteps(out, authComplete, loginRan, envFile, needsViteDotenv = false) {
|
|
@@ -4333,7 +4411,8 @@ function defaultLoginDeps() {
|
|
|
4333
4411
|
sleep: (ms) => new Promise((resolve) => {
|
|
4334
4412
|
setTimeout(resolve, ms);
|
|
4335
4413
|
}),
|
|
4336
|
-
writeEnv: defaultWriteEnv
|
|
4414
|
+
writeEnv: defaultWriteEnv,
|
|
4415
|
+
envFileName: (cwd) => (0, node_path.basename)(resolveEnvPath(cwd))
|
|
4337
4416
|
};
|
|
4338
4417
|
}
|
|
4339
4418
|
function runInitFromArgv(argv, cwd) {
|
package/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as CANONICAL_FIELDS } from "./entity-config-bC3DX9f1.mjs";
|
|
3
3
|
import "./mapping-score-C68uAqGB.mjs";
|
|
4
|
-
import { hasDefiningColumn, matchesConcept, requiredFieldsFor, scoreFieldMap, scoreRelationships, suggestCursorField, toFieldMap } from "./internal-entity-heuristics.mjs";
|
|
5
|
-
import { s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-
|
|
4
|
+
import { hasDefiningColumn, isFrameworkTable, matchesConcept, requiredFieldsFor, scoreFieldMap, scoreRelationships, suggestCursorField, toFieldMap } from "./internal-entity-heuristics.mjs";
|
|
5
|
+
import { s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-C0DU5KDS.mjs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, watch, writeFileSync } from "node:fs";
|
|
9
9
|
import { homedir, hostname } from "node:os";
|
|
10
|
-
import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
10
|
+
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
11
11
|
import { createInterface } from "node:readline/promises";
|
|
12
12
|
import { createJiti } from "jiti";
|
|
13
13
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -1656,13 +1656,14 @@ function writeCredentials(options, credentials) {
|
|
|
1656
1656
|
const entries = credentialEntries(credentials);
|
|
1657
1657
|
const result = upsertEnvValues(deps.readEnv(options.cwd), entries, { force: options.force });
|
|
1658
1658
|
deps.writeEnv(options.cwd, result.contents);
|
|
1659
|
+
const envFile = deps.envFileName?.(options.cwd) ?? ".env";
|
|
1659
1660
|
deps.out("\nLogin successful.\n");
|
|
1660
1661
|
const valuesByKey = {
|
|
1661
1662
|
[SECRET_KEY_ENV]: credentials.apiKey,
|
|
1662
1663
|
CLIVLY_SYNC_TRIGGER_SECRET: credentials.triggerSecret
|
|
1663
1664
|
};
|
|
1664
1665
|
for (const entry of entries) if (result.skipped.includes(entry.key)) deps.out(` • ${entry.key} already set — skipped (pass --force to overwrite)`);
|
|
1665
|
-
else deps.out(` ✓ ${entry.key}=${maskSecret(valuesByKey[entry.key] ?? "")} written to
|
|
1666
|
+
else deps.out(` ✓ ${entry.key}=${maskSecret(valuesByKey[entry.key] ?? "")} written to ${envFile}`);
|
|
1666
1667
|
}
|
|
1667
1668
|
async function runLogin(options) {
|
|
1668
1669
|
const { deps } = options;
|
|
@@ -1705,7 +1706,10 @@ function rankCandidates$1(tables, concept) {
|
|
|
1705
1706
|
m: matchesConcept(t.tableName, concept)
|
|
1706
1707
|
})).filter((c) => Boolean(c.m)).sort((a, b) => {
|
|
1707
1708
|
const usable = Number(hasDefiningColumn(concept, columnNames(b.t))) - Number(hasDefiningColumn(concept, columnNames(a.t)));
|
|
1708
|
-
|
|
1709
|
+
if (usable !== 0) return usable;
|
|
1710
|
+
const exact = Number(b.m.exact) - Number(a.m.exact);
|
|
1711
|
+
if (exact !== 0) return exact;
|
|
1712
|
+
return Number(isFrameworkTable(a.t.tableName)) - Number(isFrameworkTable(b.t.tableName));
|
|
1709
1713
|
}).map((c) => c.t);
|
|
1710
1714
|
}
|
|
1711
1715
|
function selectEntities(tables) {
|
|
@@ -1948,6 +1952,18 @@ export async function register() {
|
|
|
1948
1952
|
return;
|
|
1949
1953
|
}
|
|
1950
1954
|
try {
|
|
1955
|
+
// A plain import, deliberately. Next compiles this file for BOTH runtimes
|
|
1956
|
+
// and follows this into your config and on into your database driver, so a
|
|
1957
|
+
// Node-only driver breaks the EDGE build — the guard above is a runtime
|
|
1958
|
+
// check and cannot prevent that. The fix belongs in next.config, where
|
|
1959
|
+
// \`clivly init\` writes an edge-only \`config.externals\` entry; nothing here
|
|
1960
|
+
// ever executes on the edge, so the driver only has to resolve.
|
|
1961
|
+
//
|
|
1962
|
+
// Do NOT make this untraceable (a variable specifier, webpackIgnore).
|
|
1963
|
+
// That silences the build error and breaks the app in a worse way:
|
|
1964
|
+
// clivly.config is TypeScript, so an unbundled import cannot load it, and
|
|
1965
|
+
// the catch below turns that into a warning — the app boots, serves
|
|
1966
|
+
// traffic, and never checks in.
|
|
1951
1967
|
const { default: clivly } = await import("${importPath}");
|
|
1952
1968
|
await clivly.start();
|
|
1953
1969
|
} catch (error) {
|
|
@@ -2001,6 +2017,46 @@ function nextNeedsInstrumentationFlag(versionRange) {
|
|
|
2001
2017
|
}
|
|
2002
2018
|
const NEXT_INSTRUMENTATION_FLAG_STEP = `Your Next.js version reads instrumentation.ts only when it is switched on. Add this to next.config, or Clivly will never be told your app started:
|
|
2003
2019
|
experimental: { instrumentationHook: true }`;
|
|
2020
|
+
const NODE_ONLY_DRIVERS = /* @__PURE__ */ new Set([
|
|
2021
|
+
"pg",
|
|
2022
|
+
"mysql",
|
|
2023
|
+
"mysql2",
|
|
2024
|
+
"better-sqlite3",
|
|
2025
|
+
"sqlite3",
|
|
2026
|
+
"tedious",
|
|
2027
|
+
"oracledb",
|
|
2028
|
+
"ioredis"
|
|
2029
|
+
]);
|
|
2030
|
+
/**
|
|
2031
|
+
* The Node-only database drivers a config's dependency list contains.
|
|
2032
|
+
*
|
|
2033
|
+
* Next compiles instrumentation.ts for the edge runtime whether or not the app
|
|
2034
|
+
* uses it, and follows the boot hook's config import into the driver. `pg`
|
|
2035
|
+
* pulls in `pgpass` → `split2` → `stream`, none of which resolve there, so the
|
|
2036
|
+
* build fails and every route 500s — before any Clivly code runs.
|
|
2037
|
+
*
|
|
2038
|
+
* `serverExternalPackages` does NOT fix this. It applies to the Node server
|
|
2039
|
+
* bundle only; the edge pass ignores it entirely, which is why adding package
|
|
2040
|
+
* after package to it never terminates. An edge-only `config.externals` is what
|
|
2041
|
+
* works, and it is safe precisely because nothing in that bundle ever executes
|
|
2042
|
+
* — the boot hook returns early unless NEXT_RUNTIME is "nodejs".
|
|
2043
|
+
*/
|
|
2044
|
+
function nodeOnlyDrivers(dependencies) {
|
|
2045
|
+
if (!dependencies) return [];
|
|
2046
|
+
return Object.keys(dependencies).filter((name) => NODE_ONLY_DRIVERS.has(name)).sort();
|
|
2047
|
+
}
|
|
2048
|
+
/** The next.config step an app needs when its config reaches a Node driver. */
|
|
2049
|
+
function nextEdgeExternalsStep(drivers) {
|
|
2050
|
+
const list = drivers.map((d) => `"${d}"`).join(", ");
|
|
2051
|
+
return `Your database driver (${drivers.join(", ")}) cannot be bundled for Next's edge runtime, which compiles instrumentation.ts whether or not you use it. Without this, every route fails to build. Add to next.config:
|
|
2052
|
+
webpack: (config, { nextRuntime }) => {
|
|
2053
|
+
if (nextRuntime === "edge") {
|
|
2054
|
+
config.externals = [...(config.externals ?? []), ${list}];
|
|
2055
|
+
}
|
|
2056
|
+
return config;
|
|
2057
|
+
}
|
|
2058
|
+
Nothing runs on the edge — the boot hook exits early there — so the driver only has to resolve.`;
|
|
2059
|
+
}
|
|
2004
2060
|
const REMIX_BOOT_STEP = `Start Clivly when your app boots. Add this near the top of app/entry.server.tsx:
|
|
2005
2061
|
import clivly from "./clivly.config";
|
|
2006
2062
|
clivly.start().catch((error) => console.warn("[clivly] could not check in:", error));`;
|
|
@@ -2551,21 +2607,32 @@ function planEnv(args) {
|
|
|
2551
2607
|
action: envAction(addedKeys.length, existing.length)
|
|
2552
2608
|
};
|
|
2553
2609
|
}
|
|
2554
|
-
function
|
|
2610
|
+
function hostManifest(args) {
|
|
2555
2611
|
const raw = args.readFile(join(args.cwd, "package.json"));
|
|
2556
|
-
if (!raw) return;
|
|
2557
|
-
let pkg = {};
|
|
2612
|
+
if (!raw) return {};
|
|
2558
2613
|
try {
|
|
2559
|
-
|
|
2614
|
+
return JSON.parse(raw);
|
|
2560
2615
|
} catch {
|
|
2561
|
-
|
|
2616
|
+
return {};
|
|
2562
2617
|
}
|
|
2618
|
+
}
|
|
2619
|
+
function declaredNextVersion(args) {
|
|
2620
|
+
const pkg = hostManifest(args);
|
|
2563
2621
|
return pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
2564
2622
|
}
|
|
2623
|
+
function declaredDependencies(args) {
|
|
2624
|
+
return hostManifest(args).dependencies;
|
|
2625
|
+
}
|
|
2626
|
+
/** The edge-externals step, or null when no Node-only driver is declared. */
|
|
2627
|
+
function edgeExternalsStepFor(dependencies) {
|
|
2628
|
+
const drivers = nodeOnlyDrivers(dependencies);
|
|
2629
|
+
return drivers.length > 0 ? nextEdgeExternalsStep(drivers) : null;
|
|
2630
|
+
}
|
|
2565
2631
|
function planBootPatch(args) {
|
|
2566
2632
|
if (bootHookPath(args.framework, args.layout) !== null) return {
|
|
2567
2633
|
file: null,
|
|
2568
|
-
manual: args.framework === "nextjs" && nextNeedsInstrumentationFlag(declaredNextVersion(args)) ? NEXT_INSTRUMENTATION_FLAG_STEP : null
|
|
2634
|
+
manual: args.framework === "nextjs" && nextNeedsInstrumentationFlag(declaredNextVersion(args)) ? NEXT_INSTRUMENTATION_FLAG_STEP : null,
|
|
2635
|
+
extra: args.framework === "nextjs" ? edgeExternalsStepFor(declaredDependencies(args)) : null
|
|
2569
2636
|
};
|
|
2570
2637
|
if (args.framework === "remix") return {
|
|
2571
2638
|
file: null,
|
|
@@ -2611,7 +2678,7 @@ function planMutations(args) {
|
|
|
2611
2678
|
return {
|
|
2612
2679
|
files,
|
|
2613
2680
|
env: planEnv(args),
|
|
2614
|
-
manualSteps: boot.manual
|
|
2681
|
+
manualSteps: [boot.manual, boot.extra ?? null].filter((step) => step !== null)
|
|
2615
2682
|
};
|
|
2616
2683
|
}
|
|
2617
2684
|
function planWidgetMutations(args) {
|
|
@@ -2756,7 +2823,14 @@ function reportSchema(report, out) {
|
|
|
2756
2823
|
const updated = report.mappings.filter((m) => m.updated);
|
|
2757
2824
|
if (created.length > 0) out(` ✅ Mapped ${countOf(created.length, "entity", "entities")}: ${created.map((m) => m.entityKey).join(", ")}.\n`);
|
|
2758
2825
|
if (updated.length > 0) out(` ✅ Updated ${countOf(updated.length, "mapping")}: ${updated.map((m) => m.entityKey).join(", ")}.\n`);
|
|
2759
|
-
|
|
2826
|
+
const skipped = report.mappings.filter((m) => m.skipped === "dashboard-owned");
|
|
2827
|
+
if (skipped.length > 0) {
|
|
2828
|
+
out(` ↩︎ Kept the dashboard's version of ${countOf(skipped.length, "entity", "entities")}: ${skipped.map((m) => m.entityKey).join(", ")}.\n`);
|
|
2829
|
+
out(" The dashboard owns these mappings, so clivly.config did not change them.\n");
|
|
2830
|
+
out(" To let your config own one, delete it in Integrations → Mapping, then push again.\n");
|
|
2831
|
+
}
|
|
2832
|
+
const settled = report.mappings.length - skipped.length;
|
|
2833
|
+
if (settled > 0 && created.length === 0 && updated.length === 0) out(` ✅ ${countOf(settled, "mapping")} already match your config.\n`);
|
|
2760
2834
|
}
|
|
2761
2835
|
async function runPushSchema(options) {
|
|
2762
2836
|
const out = options.out ?? ((line) => process.stdout.write(line));
|
|
@@ -4011,6 +4085,10 @@ function previewMutations(deps, mutations, options) {
|
|
|
4011
4085
|
out(` ${mutations.env.action.padEnd(9)} .env\n`);
|
|
4012
4086
|
for (const key of mutations.env.addedKeys) out(` + ${key}=\n`);
|
|
4013
4087
|
}
|
|
4088
|
+
if (mutations.manualSteps.length > 0) {
|
|
4089
|
+
out("\nYou will also need to:\n");
|
|
4090
|
+
for (const step of mutations.manualSteps) out(`\n ! ${step}\n`);
|
|
4091
|
+
}
|
|
4014
4092
|
out(options.diff ? "\nRe-run without --diff to apply; --force to overwrite the files marked conflict.\n" : "\nRe-run without --dry-run to apply.\n");
|
|
4015
4093
|
}
|
|
4016
4094
|
function printNextSteps(out, authComplete, loginRan, envFile, needsViteDotenv = false) {
|
|
@@ -4332,7 +4410,8 @@ function defaultLoginDeps() {
|
|
|
4332
4410
|
sleep: (ms) => new Promise((resolve) => {
|
|
4333
4411
|
setTimeout(resolve, ms);
|
|
4334
4412
|
}),
|
|
4335
|
-
writeEnv: defaultWriteEnv
|
|
4413
|
+
writeEnv: defaultWriteEnv,
|
|
4414
|
+
envFileName: (cwd) => basename(resolveEnvPath(cwd))
|
|
4336
4415
|
};
|
|
4337
4416
|
}
|
|
4338
4417
|
function runInitFromArgv(argv, cwd) {
|
|
@@ -283,6 +283,54 @@ const SENSITIVE_COLUMNS = /* @__PURE__ */ new Set([
|
|
|
283
283
|
function hasSensitiveColumns(columns) {
|
|
284
284
|
return columns.some((column) => SENSITIVE_COLUMNS.has(column.toLowerCase().replaceAll("-", "_")));
|
|
285
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Table names the supported auth libraries create.
|
|
288
|
+
*
|
|
289
|
+
* `hasSensitiveColumns` cannot catch these. Better Auth's `organization` table
|
|
290
|
+
* is `{id, name, slug, logo}` — no credential column anywhere, a real `name`,
|
|
291
|
+
* and "organizations" is in the company vocabulary above. It is, by every
|
|
292
|
+
* signal this module has, an excellent company candidate. On `vue-academy`
|
|
293
|
+
* `clivly init` duly chose it over `campuses`, the actual business table that
|
|
294
|
+
* every student row points at, and the first sync exported rows nothing could
|
|
295
|
+
* receive.
|
|
296
|
+
*
|
|
297
|
+
* Matched on name, deliberately, and this is the inverse of the rule above:
|
|
298
|
+
* withholding a table on a wrong guess is a real loss, so that is decided on
|
|
299
|
+
* columns; *ranking* one below another is cheap to get wrong, so a name match
|
|
300
|
+
* is enough. These are fixed names the plugins create, and an app whose own
|
|
301
|
+
* business table is called `member` still ranks — just after its rivals.
|
|
302
|
+
*/
|
|
303
|
+
const FRAMEWORK_TABLE_NAMES = /* @__PURE__ */ new Set([
|
|
304
|
+
"account",
|
|
305
|
+
"session",
|
|
306
|
+
"user",
|
|
307
|
+
"verification",
|
|
308
|
+
"invitation",
|
|
309
|
+
"member",
|
|
310
|
+
"organization",
|
|
311
|
+
"team",
|
|
312
|
+
"team_member",
|
|
313
|
+
"apikey",
|
|
314
|
+
"api_key",
|
|
315
|
+
"jwks",
|
|
316
|
+
"passkey",
|
|
317
|
+
"rate_limit",
|
|
318
|
+
"ratelimit",
|
|
319
|
+
"two_factor",
|
|
320
|
+
"twofactor",
|
|
321
|
+
"__drizzle_migrations",
|
|
322
|
+
"drizzle_migrations"
|
|
323
|
+
]);
|
|
324
|
+
/**
|
|
325
|
+
* Is this table one your auth library created, rather than one you wrote?
|
|
326
|
+
*
|
|
327
|
+
* Used to rank such tables below the developer's own when both could serve a
|
|
328
|
+
* concept. Never to exclude them: mapping `user` as the contact entity is
|
|
329
|
+
* legitimate for plenty of apps, and it stays selectable.
|
|
330
|
+
*/
|
|
331
|
+
function isFrameworkTable(name) {
|
|
332
|
+
return FRAMEWORK_TABLE_NAMES.has(name.toLowerCase().replaceAll("-", "_"));
|
|
333
|
+
}
|
|
286
334
|
function metaFor(table, column) {
|
|
287
335
|
return table.columnsMeta?.find((c) => c.name === column);
|
|
288
336
|
}
|
|
@@ -497,6 +545,7 @@ function toFieldMap(scored) {
|
|
|
497
545
|
exports.TARGET_CONCEPTS = TARGET_CONCEPTS;
|
|
498
546
|
exports.hasDefiningColumn = hasDefiningColumn;
|
|
499
547
|
exports.hasSensitiveColumns = hasSensitiveColumns;
|
|
548
|
+
exports.isFrameworkTable = isFrameworkTable;
|
|
500
549
|
exports.matchesConcept = matchesConcept;
|
|
501
550
|
exports.requiredFieldsFor = requiredFieldsFor;
|
|
502
551
|
exports.scoreConcepts = scoreConcepts;
|
|
@@ -19,6 +19,14 @@ interface ScoredTable {
|
|
|
19
19
|
* They stay manually selectable — an explicit, informed choice is legitimate.
|
|
20
20
|
*/
|
|
21
21
|
declare function hasSensitiveColumns(columns: string[]): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Is this table one your auth library created, rather than one you wrote?
|
|
24
|
+
*
|
|
25
|
+
* Used to rank such tables below the developer's own when both could serve a
|
|
26
|
+
* concept. Never to exclude them: mapping `user` as the contact entity is
|
|
27
|
+
* legitimate for plenty of apps, and it stays selectable.
|
|
28
|
+
*/
|
|
29
|
+
declare function isFrameworkTable(name: string): boolean;
|
|
22
30
|
declare function hasDefiningColumn(concept: TargetConcept, columns: string[]): boolean;
|
|
23
31
|
declare function scoreConcepts(table: ScoredTable): Scored<TargetConcept>[];
|
|
24
32
|
declare function topConcept(table: ScoredTable): {
|
|
@@ -61,4 +69,4 @@ interface FieldSuggestion {
|
|
|
61
69
|
declare function scoreFieldMap(concept: TargetConcept, table: ScoredTable): Scored<FieldSuggestion>[];
|
|
62
70
|
declare function toFieldMap(scored: Scored<FieldSuggestion>[]): Record<string, string>;
|
|
63
71
|
//#endregion
|
|
64
|
-
export { type DiscoveredColumnMeta, FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
72
|
+
export { type DiscoveredColumnMeta, FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, isFrameworkTable, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
@@ -19,6 +19,14 @@ interface ScoredTable {
|
|
|
19
19
|
* They stay manually selectable — an explicit, informed choice is legitimate.
|
|
20
20
|
*/
|
|
21
21
|
declare function hasSensitiveColumns(columns: string[]): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Is this table one your auth library created, rather than one you wrote?
|
|
24
|
+
*
|
|
25
|
+
* Used to rank such tables below the developer's own when both could serve a
|
|
26
|
+
* concept. Never to exclude them: mapping `user` as the contact entity is
|
|
27
|
+
* legitimate for plenty of apps, and it stays selectable.
|
|
28
|
+
*/
|
|
29
|
+
declare function isFrameworkTable(name: string): boolean;
|
|
22
30
|
declare function hasDefiningColumn(concept: TargetConcept, columns: string[]): boolean;
|
|
23
31
|
declare function scoreConcepts(table: ScoredTable): Scored<TargetConcept>[];
|
|
24
32
|
declare function topConcept(table: ScoredTable): {
|
|
@@ -61,4 +69,4 @@ interface FieldSuggestion {
|
|
|
61
69
|
declare function scoreFieldMap(concept: TargetConcept, table: ScoredTable): Scored<FieldSuggestion>[];
|
|
62
70
|
declare function toFieldMap(scored: Scored<FieldSuggestion>[]): Record<string, string>;
|
|
63
71
|
//#endregion
|
|
64
|
-
export { type DiscoveredColumnMeta, FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
72
|
+
export { type DiscoveredColumnMeta, FieldSuggestion, RelationshipSuggestion, ScoredTable, SuggestedVia, TARGET_CONCEPTS, TargetConcept, hasDefiningColumn, hasSensitiveColumns, isFrameworkTable, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
@@ -282,6 +282,54 @@ const SENSITIVE_COLUMNS = /* @__PURE__ */ new Set([
|
|
|
282
282
|
function hasSensitiveColumns(columns) {
|
|
283
283
|
return columns.some((column) => SENSITIVE_COLUMNS.has(column.toLowerCase().replaceAll("-", "_")));
|
|
284
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Table names the supported auth libraries create.
|
|
287
|
+
*
|
|
288
|
+
* `hasSensitiveColumns` cannot catch these. Better Auth's `organization` table
|
|
289
|
+
* is `{id, name, slug, logo}` — no credential column anywhere, a real `name`,
|
|
290
|
+
* and "organizations" is in the company vocabulary above. It is, by every
|
|
291
|
+
* signal this module has, an excellent company candidate. On `vue-academy`
|
|
292
|
+
* `clivly init` duly chose it over `campuses`, the actual business table that
|
|
293
|
+
* every student row points at, and the first sync exported rows nothing could
|
|
294
|
+
* receive.
|
|
295
|
+
*
|
|
296
|
+
* Matched on name, deliberately, and this is the inverse of the rule above:
|
|
297
|
+
* withholding a table on a wrong guess is a real loss, so that is decided on
|
|
298
|
+
* columns; *ranking* one below another is cheap to get wrong, so a name match
|
|
299
|
+
* is enough. These are fixed names the plugins create, and an app whose own
|
|
300
|
+
* business table is called `member` still ranks — just after its rivals.
|
|
301
|
+
*/
|
|
302
|
+
const FRAMEWORK_TABLE_NAMES = /* @__PURE__ */ new Set([
|
|
303
|
+
"account",
|
|
304
|
+
"session",
|
|
305
|
+
"user",
|
|
306
|
+
"verification",
|
|
307
|
+
"invitation",
|
|
308
|
+
"member",
|
|
309
|
+
"organization",
|
|
310
|
+
"team",
|
|
311
|
+
"team_member",
|
|
312
|
+
"apikey",
|
|
313
|
+
"api_key",
|
|
314
|
+
"jwks",
|
|
315
|
+
"passkey",
|
|
316
|
+
"rate_limit",
|
|
317
|
+
"ratelimit",
|
|
318
|
+
"two_factor",
|
|
319
|
+
"twofactor",
|
|
320
|
+
"__drizzle_migrations",
|
|
321
|
+
"drizzle_migrations"
|
|
322
|
+
]);
|
|
323
|
+
/**
|
|
324
|
+
* Is this table one your auth library created, rather than one you wrote?
|
|
325
|
+
*
|
|
326
|
+
* Used to rank such tables below the developer's own when both could serve a
|
|
327
|
+
* concept. Never to exclude them: mapping `user` as the contact entity is
|
|
328
|
+
* legitimate for plenty of apps, and it stays selectable.
|
|
329
|
+
*/
|
|
330
|
+
function isFrameworkTable(name) {
|
|
331
|
+
return FRAMEWORK_TABLE_NAMES.has(name.toLowerCase().replaceAll("-", "_"));
|
|
332
|
+
}
|
|
285
333
|
function metaFor(table, column) {
|
|
286
334
|
return table.columnsMeta?.find((c) => c.name === column);
|
|
287
335
|
}
|
|
@@ -493,4 +541,4 @@ function toFieldMap(scored) {
|
|
|
493
541
|
return map;
|
|
494
542
|
}
|
|
495
543
|
//#endregion
|
|
496
|
-
export { TARGET_CONCEPTS, hasDefiningColumn, hasSensitiveColumns, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
544
|
+
export { TARGET_CONCEPTS, hasDefiningColumn, hasSensitiveColumns, isFrameworkTable, matchesConcept, requiredFieldsFor, scoreConcepts, scoreFieldMap, scoreRelationships, suggestCursorField, suggestDiscriminatorColumns, toFieldMap, topConcept, validateMapping };
|
|
@@ -332,6 +332,22 @@ interface SyncSource {
|
|
|
332
332
|
* absent from another, and read by the runtime.
|
|
333
333
|
*/
|
|
334
334
|
objectType?: string;
|
|
335
|
+
/**
|
|
336
|
+
* The `source` label a push carries, when it differs from `entity`.
|
|
337
|
+
*
|
|
338
|
+
* Only a projection sets this, and only because a projection entity has no
|
|
339
|
+
* choice but to carry two names: its entity key doubles as the custom-object
|
|
340
|
+
* slug and must be kebab-case, while `projection.entityKey` is validated by
|
|
341
|
+
* `/^[a-z][a-z0-9_]*$/` and rejects hyphens. The server resolves that by
|
|
342
|
+
* storing the mapping's `sourceTable` as `projection.baseTable` — so a push
|
|
343
|
+
* labelled with the entity key matched no mapping and every row was dropped,
|
|
344
|
+
* while `push-schema` reported the mapping created and exited 0.
|
|
345
|
+
*
|
|
346
|
+
* Deliberately NOT folded into `entity`: that value is also the cursor-store
|
|
347
|
+
* key, so redefining it would silently reset the sync cursor of every
|
|
348
|
+
* existing projection and re-push its whole history.
|
|
349
|
+
*/
|
|
350
|
+
pushSource?: string;
|
|
335
351
|
/** Set by `fromDrizzle`; lets `doctor` introspect the backing table. */
|
|
336
352
|
[DRIZZLE_META]?: DrizzleSourceMeta;
|
|
337
353
|
}
|
|
@@ -332,6 +332,22 @@ interface SyncSource {
|
|
|
332
332
|
* absent from another, and read by the runtime.
|
|
333
333
|
*/
|
|
334
334
|
objectType?: string;
|
|
335
|
+
/**
|
|
336
|
+
* The `source` label a push carries, when it differs from `entity`.
|
|
337
|
+
*
|
|
338
|
+
* Only a projection sets this, and only because a projection entity has no
|
|
339
|
+
* choice but to carry two names: its entity key doubles as the custom-object
|
|
340
|
+
* slug and must be kebab-case, while `projection.entityKey` is validated by
|
|
341
|
+
* `/^[a-z][a-z0-9_]*$/` and rejects hyphens. The server resolves that by
|
|
342
|
+
* storing the mapping's `sourceTable` as `projection.baseTable` — so a push
|
|
343
|
+
* labelled with the entity key matched no mapping and every row was dropped,
|
|
344
|
+
* while `push-schema` reported the mapping created and exited 0.
|
|
345
|
+
*
|
|
346
|
+
* Deliberately NOT folded into `entity`: that value is also the cursor-store
|
|
347
|
+
* key, so redefining it would silently reset the sync cursor of every
|
|
348
|
+
* existing projection and re-push its whole history.
|
|
349
|
+
*/
|
|
350
|
+
pushSource?: string;
|
|
335
351
|
/** Set by `fromDrizzle`; lets `doctor` introspect the backing table. */
|
|
336
352
|
[DRIZZLE_META]?: DrizzleSourceMeta;
|
|
337
353
|
}
|
package/package.json
CHANGED
|
@@ -77,6 +77,43 @@ function resolveEntitySource(config, entityKey) {
|
|
|
77
77
|
function expectedSourceKey(concept) {
|
|
78
78
|
return CONCEPT_KEYS[concept] ?? "custom";
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* The `source` label a push must carry for the server to find its mapping.
|
|
82
|
+
*
|
|
83
|
+
* `entity` is the SDK's own name for a source — the cursor-store key, and what
|
|
84
|
+
* the developer wrote in `config.source`. It is NOT always what the server
|
|
85
|
+
* filed the mapping under: for a projection, `ensureSchema` stores
|
|
86
|
+
* `sourceTable` as `projection.baseTable`, because a projection entity's key
|
|
87
|
+
* doubles as a kebab-case object slug and cannot also be the snake_case
|
|
88
|
+
* `projection.entityKey`.
|
|
89
|
+
*
|
|
90
|
+
* Pushing under `entity` therefore dropped 100% of a projection's rows while
|
|
91
|
+
* every local signal said the setup was correct. One function so the three
|
|
92
|
+
* push sites cannot drift apart again.
|
|
93
|
+
*/
|
|
94
|
+
function pushSourceOf(source) {
|
|
95
|
+
return source.pushSource ?? source.entity;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The push label for a custom source, resolved against the config.
|
|
99
|
+
*
|
|
100
|
+
* `fromDrizzleProjection` stamps `pushSource` itself, but a projection source
|
|
101
|
+
* can equally be hand-built — `fromDrizzle` with an overridden `fetchPage` is
|
|
102
|
+
* what the README's own example leads you to, and next-finance did exactly
|
|
103
|
+
* that. Such a source has no `pushSource`, so the label has to come from the
|
|
104
|
+
* one place that is true either way: the entity's declared
|
|
105
|
+
* `projection.baseTable`.
|
|
106
|
+
*
|
|
107
|
+
* Matched on the source's `entity`, which for a custom source is also the
|
|
108
|
+
* config entity key (that is what `resolveEntitySource` above pairs them by).
|
|
109
|
+
* Falls back to `pushSourceOf`, so a non-projection custom source and an
|
|
110
|
+
* already-stamped one both keep their current label.
|
|
111
|
+
*/
|
|
112
|
+
function customPushSource(config, source) {
|
|
113
|
+
if (source.pushSource) return source.pushSource;
|
|
114
|
+
const entities = config.entities?.entities;
|
|
115
|
+
return (entities?.[source.entity] ?? Object.values(entities ?? {}).find((e) => e.source === source.entity))?.projection?.baseTable ?? source.entity;
|
|
116
|
+
}
|
|
80
117
|
//#endregion
|
|
81
118
|
//#region ../sdk/src/concept-coverage.ts
|
|
82
119
|
const CHECK_NAME$1 = "Entity → source coverage";
|
|
@@ -1203,7 +1240,7 @@ async function pushRemoteProjections(args) {
|
|
|
1203
1240
|
}
|
|
1204
1241
|
try {
|
|
1205
1242
|
const ok = await args.push(SYNC_CUSTOM_PATH$1, {
|
|
1206
|
-
source: source
|
|
1243
|
+
source: pushSourceOf(source),
|
|
1207
1244
|
rows: all,
|
|
1208
1245
|
mode: "full",
|
|
1209
1246
|
fetched: all.length,
|
|
@@ -1278,7 +1315,7 @@ async function syncSource(args) {
|
|
|
1278
1315
|
if (rows.length === 0) break;
|
|
1279
1316
|
const capped = maxRows === void 0 ? rows : rows.slice(0, maxRows);
|
|
1280
1317
|
if (!await push(path, {
|
|
1281
|
-
source: source
|
|
1318
|
+
source: pushSourceOf(source),
|
|
1282
1319
|
rows: capped,
|
|
1283
1320
|
mode: "delta",
|
|
1284
1321
|
fetched: rows.length,
|
|
@@ -1322,7 +1359,7 @@ async function fullSyncSource(args) {
|
|
|
1322
1359
|
ok: true
|
|
1323
1360
|
};
|
|
1324
1361
|
const ok = await push(path, {
|
|
1325
|
-
source: source
|
|
1362
|
+
source: pushSourceOf(source),
|
|
1326
1363
|
rows: all,
|
|
1327
1364
|
mode: "full",
|
|
1328
1365
|
fetched: all.length,
|
|
@@ -1922,7 +1959,10 @@ function createClivlySDK(config) {
|
|
|
1922
1959
|
};
|
|
1923
1960
|
const syncCustomSources = async (customSources, mode, runId) => {
|
|
1924
1961
|
const failed = [];
|
|
1925
|
-
for (const customSource of customSources ?? [])
|
|
1962
|
+
for (const customSource of customSources ?? []) {
|
|
1963
|
+
customSource.pushSource = customPushSource(config, customSource);
|
|
1964
|
+
if (!await syncOne(customSource, SYNC_CUSTOM_PATH, mode, customSource.objectType, runId)) failed.push(customSource.entity);
|
|
1965
|
+
}
|
|
1926
1966
|
return failed;
|
|
1927
1967
|
};
|
|
1928
1968
|
const syncRemoteSources = async (sources, runId) => {
|
|
@@ -77,6 +77,43 @@ function resolveEntitySource(config, entityKey) {
|
|
|
77
77
|
function expectedSourceKey(concept) {
|
|
78
78
|
return CONCEPT_KEYS[concept] ?? "custom";
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* The `source` label a push must carry for the server to find its mapping.
|
|
82
|
+
*
|
|
83
|
+
* `entity` is the SDK's own name for a source — the cursor-store key, and what
|
|
84
|
+
* the developer wrote in `config.source`. It is NOT always what the server
|
|
85
|
+
* filed the mapping under: for a projection, `ensureSchema` stores
|
|
86
|
+
* `sourceTable` as `projection.baseTable`, because a projection entity's key
|
|
87
|
+
* doubles as a kebab-case object slug and cannot also be the snake_case
|
|
88
|
+
* `projection.entityKey`.
|
|
89
|
+
*
|
|
90
|
+
* Pushing under `entity` therefore dropped 100% of a projection's rows while
|
|
91
|
+
* every local signal said the setup was correct. One function so the three
|
|
92
|
+
* push sites cannot drift apart again.
|
|
93
|
+
*/
|
|
94
|
+
function pushSourceOf(source) {
|
|
95
|
+
return source.pushSource ?? source.entity;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The push label for a custom source, resolved against the config.
|
|
99
|
+
*
|
|
100
|
+
* `fromDrizzleProjection` stamps `pushSource` itself, but a projection source
|
|
101
|
+
* can equally be hand-built — `fromDrizzle` with an overridden `fetchPage` is
|
|
102
|
+
* what the README's own example leads you to, and next-finance did exactly
|
|
103
|
+
* that. Such a source has no `pushSource`, so the label has to come from the
|
|
104
|
+
* one place that is true either way: the entity's declared
|
|
105
|
+
* `projection.baseTable`.
|
|
106
|
+
*
|
|
107
|
+
* Matched on the source's `entity`, which for a custom source is also the
|
|
108
|
+
* config entity key (that is what `resolveEntitySource` above pairs them by).
|
|
109
|
+
* Falls back to `pushSourceOf`, so a non-projection custom source and an
|
|
110
|
+
* already-stamped one both keep their current label.
|
|
111
|
+
*/
|
|
112
|
+
function customPushSource(config, source) {
|
|
113
|
+
if (source.pushSource) return source.pushSource;
|
|
114
|
+
const entities = config.entities?.entities;
|
|
115
|
+
return (entities?.[source.entity] ?? Object.values(entities ?? {}).find((e) => e.source === source.entity))?.projection?.baseTable ?? source.entity;
|
|
116
|
+
}
|
|
80
117
|
//#endregion
|
|
81
118
|
//#region ../sdk/src/concept-coverage.ts
|
|
82
119
|
const CHECK_NAME$1 = "Entity → source coverage";
|
|
@@ -1203,7 +1240,7 @@ async function pushRemoteProjections(args) {
|
|
|
1203
1240
|
}
|
|
1204
1241
|
try {
|
|
1205
1242
|
const ok = await args.push(SYNC_CUSTOM_PATH$1, {
|
|
1206
|
-
source: source
|
|
1243
|
+
source: pushSourceOf(source),
|
|
1207
1244
|
rows: all,
|
|
1208
1245
|
mode: "full",
|
|
1209
1246
|
fetched: all.length,
|
|
@@ -1278,7 +1315,7 @@ async function syncSource(args) {
|
|
|
1278
1315
|
if (rows.length === 0) break;
|
|
1279
1316
|
const capped = maxRows === void 0 ? rows : rows.slice(0, maxRows);
|
|
1280
1317
|
if (!await push(path, {
|
|
1281
|
-
source: source
|
|
1318
|
+
source: pushSourceOf(source),
|
|
1282
1319
|
rows: capped,
|
|
1283
1320
|
mode: "delta",
|
|
1284
1321
|
fetched: rows.length,
|
|
@@ -1322,7 +1359,7 @@ async function fullSyncSource(args) {
|
|
|
1322
1359
|
ok: true
|
|
1323
1360
|
};
|
|
1324
1361
|
const ok = await push(path, {
|
|
1325
|
-
source: source
|
|
1362
|
+
source: pushSourceOf(source),
|
|
1326
1363
|
rows: all,
|
|
1327
1364
|
mode: "full",
|
|
1328
1365
|
fetched: all.length,
|
|
@@ -1922,7 +1959,10 @@ function createClivlySDK(config) {
|
|
|
1922
1959
|
};
|
|
1923
1960
|
const syncCustomSources = async (customSources, mode, runId) => {
|
|
1924
1961
|
const failed = [];
|
|
1925
|
-
for (const customSource of customSources ?? [])
|
|
1962
|
+
for (const customSource of customSources ?? []) {
|
|
1963
|
+
customSource.pushSource = customPushSource(config, customSource);
|
|
1964
|
+
if (!await syncOne(customSource, SYNC_CUSTOM_PATH, mode, customSource.objectType, runId)) failed.push(customSource.entity);
|
|
1965
|
+
}
|
|
1926
1966
|
return failed;
|
|
1927
1967
|
};
|
|
1928
1968
|
const syncRemoteSources = async (sources, runId) => {
|
package/sdk.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_entity_config = require("./entity-config-C2MfIDL2.cjs");
|
|
3
|
-
const require_sdk = require("./sdk-
|
|
3
|
+
const require_sdk = require("./sdk-BAHcnhm7.cjs");
|
|
4
4
|
exports.ClivlyAuthError = require_sdk.ClivlyAuthError;
|
|
5
5
|
exports.ClivlyError = require_sdk.ClivlyError;
|
|
6
6
|
exports.ClivlyNetworkError = require_sdk.ClivlyNetworkError;
|
package/sdk.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { C as customObjectDeclarations, M as validateEntitiesConfig, a as ClivlyEntitiesConfig, f as CustomObjectDeclaration, l as ConfigValidationError, m as DiscoveredSchemaTable, u as ConfigValidationResult, w as defineClivlyConfig } from "./entity-config-w8oMBWf8.cjs";
|
|
2
|
-
import { _ as RemoteProjectionsDescription, a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, g as EnsuredObjectTypeResult, h as EnsuredMappingResult, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, m as EnsureSchemaResult, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore, y as SyncTriggerDescription } from "./namespace-probe-
|
|
2
|
+
import { _ as RemoteProjectionsDescription, a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, g as EnsuredObjectTypeResult, h as EnsuredMappingResult, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, m as EnsureSchemaResult, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore, y as SyncTriggerDescription } from "./namespace-probe-C3xMOqWE.cjs";
|
|
3
3
|
//#region ../sdk/src/trigger-url.d.ts
|
|
4
4
|
declare function isPlaceholderUrl(value: string): boolean;
|
|
5
5
|
//#endregion
|
package/sdk.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { C as customObjectDeclarations, M as validateEntitiesConfig, a as ClivlyEntitiesConfig, f as CustomObjectDeclaration, l as ConfigValidationError, m as DiscoveredSchemaTable, u as ConfigValidationResult, w as defineClivlyConfig } from "./entity-config-w8oMBWf8.mjs";
|
|
2
|
-
import { _ as RemoteProjectionsDescription, a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, g as EnsuredObjectTypeResult, h as EnsuredMappingResult, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, m as EnsureSchemaResult, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore, y as SyncTriggerDescription } from "./namespace-probe-
|
|
2
|
+
import { _ as RemoteProjectionsDescription, a as ClivlyConnectResult, c as ClivlyUser, d as DiscoveredTable, f as DoctorCheck, g as EnsuredObjectTypeResult, h as EnsuredMappingResult, i as ClivlyConnectReason, l as CreateChatSessionHandlerOptions, m as EnsureSchemaResult, n as ChatSessionRequestBody, o as ClivlySDK, p as DoctorReport, r as ChatWidgetVisitor, s as ClivlySDKConfig, u as CursorStore, y as SyncTriggerDescription } from "./namespace-probe-BmxtnOfc.mjs";
|
|
3
3
|
//#region ../sdk/src/trigger-url.d.ts
|
|
4
4
|
declare function isPlaceholderUrl(value: string): boolean;
|
|
5
5
|
//#endregion
|
package/sdk.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { c as defineClivlyConfig, g as validateEntitiesConfig, s as customObjectDeclarations } from "./entity-config-bC3DX9f1.mjs";
|
|
2
|
-
import { a as ClivlyRateLimitError, i as ClivlyNetworkError, n as ClivlyAuthError, o as createChatSessionHandler, r as ClivlyError, s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-
|
|
2
|
+
import { a as ClivlyRateLimitError, i as ClivlyNetworkError, n as ClivlyAuthError, o as createChatSessionHandler, r as ClivlyError, s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-C0DU5KDS.mjs";
|
|
3
3
|
export { ClivlyAuthError, ClivlyError, ClivlyNetworkError, ClivlyRateLimitError, createChatSessionHandler, createClivlySDK, customObjectDeclarations, defineClivlyConfig, isPlaceholderUrl, validateEntitiesConfig };
|
package/vite.d.cts
CHANGED
package/vite.d.mts
CHANGED