clivly 0.9.5 → 0.9.7
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 +22 -3
- package/core-auth-adapter.cjs +1 -1
- package/core-auth-adapter.mjs +1 -1
- package/core-drizzle.cjs +1 -1
- package/core-drizzle.d.cts +1 -1
- package/core-drizzle.d.mts +1 -1
- package/core-drizzle.mjs +1 -1
- package/core-entity-config.cjs +1 -1
- package/core-entity-config.mjs +1 -1
- package/core-mapping-form.cjs +1 -1
- package/core-mapping-form.mjs +1 -1
- package/core-mappings-config.cjs +1 -1
- package/core-mappings-config.mjs +1 -1
- package/core-sync-engine.cjs +1 -1
- package/core-sync-engine.mjs +1 -1
- package/core-view-compiler.cjs +1 -1
- package/core-view-compiler.mjs +1 -1
- package/core.cjs +14 -9
- package/core.d.cts +2 -2
- package/core.d.mts +2 -2
- package/core.mjs +9 -9
- package/{drizzle-CiK8MMEZ.d.mts → drizzle-BmDDgAIF.d.mts} +3 -1
- package/{drizzle-bfjVzYR6.cjs → drizzle-DcB7OfBp.cjs} +9 -1
- package/{drizzle-DEtRrqtc.d.cts → drizzle-ixVV4pPN.d.cts} +3 -1
- package/{drizzle-3kvhkwO7.mjs → drizzle-wbcuG-cy.mjs} +9 -1
- package/drizzle.cjs +61 -10
- package/drizzle.d.cts +9 -3
- package/drizzle.d.mts +9 -3
- package/drizzle.mjs +61 -10
- package/{entity-config-B74SPOUZ.mjs → entity-config-C8IIkrmX.mjs} +34 -5
- package/{entity-config-7sGCk_nw.cjs → entity-config-CPM-Tp-d.cjs} +34 -5
- package/index.cjs +41 -9
- package/index.mjs +41 -9
- package/internal-BLKMnIIZ.cjs +259 -0
- package/internal-BpVh_J77.mjs +212 -0
- package/{internal-o2M7FGlR.d.mts → internal-CvlFk3KC.d.mts} +82 -2
- package/{internal-wCXSELDN.d.cts → internal-DGUOLQAq.d.cts} +82 -2
- package/internal-drizzle.cjs +1 -1
- package/internal-drizzle.d.cts +1 -1
- package/internal-drizzle.d.mts +1 -1
- package/internal-drizzle.mjs +1 -1
- package/internal-entity-config.cjs +1 -1
- package/internal-entity-config.mjs +1 -1
- package/internal-entity-heuristics.cjs +1 -1
- package/internal-entity-heuristics.mjs +1 -1
- package/internal-mappings-config.cjs +1 -1
- package/internal-mappings-config.mjs +1 -1
- package/internal-sync-engine.cjs +1 -1
- package/internal-sync-engine.mjs +1 -1
- package/internal-view-compiler.cjs +1 -1
- package/internal-view-compiler.mjs +1 -1
- package/internal.cjs +13 -7
- package/internal.d.cts +2 -2
- package/internal.d.mts +2 -2
- package/internal.mjs +8 -8
- package/{mappings-config-CLyEg0sf.cjs → mappings-config-CBwL3FVh.cjs} +1 -1
- package/{mappings-config-DQS_s4dg.mjs → mappings-config-DUKBe6OS.mjs} +1 -1
- package/{namespace-probe-Cn7voAd0.d.mts → namespace-probe-BQ4imnV7.d.mts} +85 -2
- package/{namespace-probe-BoGI18dQ.d.cts → namespace-probe-DZIGdvlm.d.cts} +85 -2
- package/package.json +1 -1
- package/{projection-Dz6_i1-w.cjs → projection-DEwL-D8R.cjs} +6 -0
- package/{projection-CSGJbZlZ.mjs → projection-hnPSgH6G.mjs} +1 -1
- package/{projection-sql-BgnfiGqF.mjs → projection-sql-9zzTVGvf.mjs} +34 -2
- package/{projection-sql-Cl1qIn8m.cjs → projection-sql-iHy-Cx6V.cjs} +45 -1
- package/{sdk-CM1UO-I7.cjs → sdk-BL9BCzyD.cjs} +354 -20
- package/{sdk-DYBDR939.mjs → sdk-CubnsyD9.mjs} +354 -20
- package/sdk.cjs +2 -2
- package/sdk.d.cts +2 -2
- package/sdk.d.mts +2 -2
- package/sdk.mjs +2 -2
- package/{sync-engine-C4A5jgA4.cjs → sync-engine-BLgwLpR6.cjs} +1 -1
- package/{sync-engine-Dt5QQSKO.mjs → sync-engine-B_IzoUSx.mjs} +1 -1
- package/{view-compiler-B5nDal14.cjs → view-compiler-BHI5E4QI.cjs} +2 -2
- package/{view-compiler-CSYwEWf5.mjs → view-compiler-D_6gfXFr.mjs} +2 -2
- package/vite.d.cts +1 -1
- package/vite.d.mts +1 -1
- package/internal-CykXpEGk.mjs +0 -68
- package/internal-DqduZU3l.cjs +0 -97
package/drizzle.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as BASE_REF } from "./projection-
|
|
1
|
+
import { t as BASE_REF } from "./projection-hnPSgH6G.mjs";
|
|
2
2
|
import { t as DRIZZLE_META } from "./drizzle-meta-D-5k3n0i.mjs";
|
|
3
|
-
import { a as
|
|
4
|
-
import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-
|
|
3
|
+
import { a as compileProjectionPreview, i as compileProjectionJoins, n as compileProjectionFrom, r as compileProjectionIdentity, s as quoteIdent } from "./projection-sql-9zzTVGvf.mjs";
|
|
4
|
+
import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-wbcuG-cy.mjs";
|
|
5
5
|
import { asc, getTableColumns, getTableName, sql } from "drizzle-orm";
|
|
6
6
|
//#region ../sdk/src/drizzle-projection.ts
|
|
7
7
|
/** `<ref>.<column>` — the token `fields` carries and cloud-ingest reads. */
|
|
@@ -39,10 +39,12 @@ function fromDrizzleProjection(db, projection, opts) {
|
|
|
39
39
|
const client = db;
|
|
40
40
|
const known = /* @__PURE__ */ new Set([BASE_REF, ...projection.joins.map((j) => j.key)]);
|
|
41
41
|
if (projection.identity.kind === "base_pk" && projection.identity.column === void 0 && opts.basePkColumn === void 0) throw new Error(`fromDrizzleProjection: projection "${projection.entityKey}" has a base_pk identity with no column — pass { basePkColumn }.`);
|
|
42
|
-
const cursorRef = parseRef(opts.cursorField);
|
|
43
|
-
assertKnownRef(cursorRef, known, "cursorField");
|
|
44
42
|
for (const [field, binding] of Object.entries(projection.fieldBindings)) assertKnownRef(binding.from, known, `field "${field}"`);
|
|
45
43
|
const identityExpr = compileProjectionIdentity(projection, opts.basePkColumn ?? "");
|
|
44
|
+
const fromClause = [compileProjectionFrom(projection), ...compileProjectionJoins(projection)].join(" ");
|
|
45
|
+
if (opts.cursorField === void 0) return fromDrizzleProjectionByIdentity(client, projection, identityExpr, fromClause, opts);
|
|
46
|
+
const cursorRef = parseRef(opts.cursorField);
|
|
47
|
+
assertKnownRef(cursorRef, known, "cursorField");
|
|
46
48
|
const cursorToken = refToken(cursorRef);
|
|
47
49
|
const cursorExpr = `CAST(${refSql(cursorRef)} AS timestamptz)`;
|
|
48
50
|
const cursorRead = `to_char(${cursorExpr} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
|
|
@@ -54,11 +56,7 @@ function fromDrizzleProjection(db, projection, opts) {
|
|
|
54
56
|
emitted.add(token);
|
|
55
57
|
selectItems.push(`${refSql(binding.from)} AS ${quoteIdent(token)}`);
|
|
56
58
|
}
|
|
57
|
-
const head = [
|
|
58
|
-
`select ${selectItems.join(", ")}`,
|
|
59
|
-
compileProjectionFrom(projection),
|
|
60
|
-
...compileProjectionJoins(projection)
|
|
61
|
-
].join(" ");
|
|
59
|
+
const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
|
|
62
60
|
return {
|
|
63
61
|
entity: opts.entity,
|
|
64
62
|
cursorField: cursorToken,
|
|
@@ -71,6 +69,49 @@ function fromDrizzleProjection(db, projection, opts) {
|
|
|
71
69
|
}
|
|
72
70
|
};
|
|
73
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* The identity-only paging path `fromDrizzleProjection` takes when
|
|
74
|
+
* `cursorField` is omitted. Deliberately not a generalisation of the
|
|
75
|
+
* timestamp path above — it selects no timestamp cursor, pages on the
|
|
76
|
+
* identity expression alone, and always reports `cursorField`/`idField` as
|
|
77
|
+
* `"id"`, the alias every row carries either way.
|
|
78
|
+
*/
|
|
79
|
+
function fromDrizzleProjectionByIdentity(client, projection, identityExpr, fromClause, opts) {
|
|
80
|
+
const selectItems = [`${identityExpr} AS ${quoteIdent("id")}`];
|
|
81
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
82
|
+
for (const binding of Object.values(projection.fieldBindings)) {
|
|
83
|
+
const token = refToken(binding.from);
|
|
84
|
+
if (emitted.has(token)) continue;
|
|
85
|
+
emitted.add(token);
|
|
86
|
+
selectItems.push(`${refSql(binding.from)} AS ${quoteIdent(token)}`);
|
|
87
|
+
}
|
|
88
|
+
const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
|
|
89
|
+
return {
|
|
90
|
+
entity: opts.entity,
|
|
91
|
+
cursorField: "id",
|
|
92
|
+
idField: "id",
|
|
93
|
+
...opts.objectType === void 0 ? {} : { objectType: opts.objectType },
|
|
94
|
+
fetchPage: async ({ since, limit }) => {
|
|
95
|
+
const where = since ? sql` where ${sql.raw(identityExpr)} > ${since.id}` : sql``;
|
|
96
|
+
const query = sql`${sql.raw(head)}${where} order by ${sql.raw(identityExpr)} asc limit ${limit}`;
|
|
97
|
+
return normalizeRows(await client.execute(query));
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region ../sdk/src/projection-preview.ts
|
|
103
|
+
/** `db.execute` returns either an array or `{ rows }`, depending on driver. */
|
|
104
|
+
function rowsOf(result) {
|
|
105
|
+
if (Array.isArray(result)) return result;
|
|
106
|
+
const rows = result.rows;
|
|
107
|
+
if (Array.isArray(rows)) return rows;
|
|
108
|
+
throw new Error("previewProjection: db.execute() returned neither an array of rows nor { rows }.");
|
|
109
|
+
}
|
|
110
|
+
async function previewProjection(db, projection, opts) {
|
|
111
|
+
const client = db;
|
|
112
|
+
const { sql } = await import("drizzle-orm");
|
|
113
|
+
return rowsOf(await client.execute(sql.raw(compileProjectionPreview(projection, opts.limit))));
|
|
114
|
+
}
|
|
74
115
|
//#endregion
|
|
75
116
|
//#region ../sdk/src/drizzle.ts
|
|
76
117
|
function inferIdField(table) {
|
|
@@ -134,6 +175,16 @@ function drizzleIntrospector(db) {
|
|
|
134
175
|
},
|
|
135
176
|
async sampleSelect(table) {
|
|
136
177
|
await client.execute(sql`select 1 from ${sql.identifier(table)} limit 1`);
|
|
178
|
+
},
|
|
179
|
+
runProjection(projection, opts) {
|
|
180
|
+
return previewProjection(db, projection, opts);
|
|
181
|
+
},
|
|
182
|
+
projectionSource(projection, opts) {
|
|
183
|
+
return fromDrizzleProjection(db, projection, {
|
|
184
|
+
entity: opts.objectType,
|
|
185
|
+
objectType: opts.objectType,
|
|
186
|
+
...opts.basePkColumn ? { basePkColumn: opts.basePkColumn } : {}
|
|
187
|
+
});
|
|
137
188
|
}
|
|
138
189
|
};
|
|
139
190
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as projectionSchema, c as boolean, d as record, f as strictObject, i as identityRefs, l as literal, m as union, o as _enum, p as string, s as array, t as BASE_REF, u as number } from "./projection-hnPSgH6G.mjs";
|
|
2
2
|
//#region ../core/src/schema-meta.ts
|
|
3
3
|
/**
|
|
4
4
|
* Accessors over a discovered table's schema-v2 metadata.
|
|
@@ -137,7 +137,7 @@ function checkTablesExist(errors, projection, resolver, base) {
|
|
|
137
137
|
if (!hasRichMeta(table)) {
|
|
138
138
|
errors.push({
|
|
139
139
|
path,
|
|
140
|
-
message: `table "${name}"
|
|
140
|
+
message: `table "${name}" was discovered without column types or unique keys, which a projection needs to prove its id is unique. Redeploy or restart the app so it re-reports its schema (or run \`clivly push-schema\`); if the table is declared by hand as \`{ name, columns }\`, use \`discoverFromDrizzle\` so keys and types come with it.`
|
|
141
141
|
});
|
|
142
142
|
ok = false;
|
|
143
143
|
}
|
|
@@ -182,10 +182,27 @@ function checkRefs(errors, projection, resolver, base, basePk) {
|
|
|
182
182
|
for (const [field, binding] of Object.entries(projection.fieldBindings)) checkRef(errors, resolver, `${base}.projection.fieldBindings.${field}`, binding.from);
|
|
183
183
|
for (const ref of identityRefs(projection.identity, basePk)) checkRef(errors, resolver, `${base}.projection.identity`, ref);
|
|
184
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Prove that a single-column identity (`column`, or `base_pk` with an
|
|
187
|
+
* explicit column) is actually unique.
|
|
188
|
+
*
|
|
189
|
+
* `coversUniqueKey` fails closed on absent metadata, same as every other
|
|
190
|
+
* check in this file: a column not provably covered by a unique key is
|
|
191
|
+
* rejected, never assumed safe.
|
|
192
|
+
*/
|
|
193
|
+
function checkSingleColumnIdentity(errors, resolver, path, ref) {
|
|
194
|
+
const table = resolver.tableFor(ref.table);
|
|
195
|
+
if (!table) return;
|
|
196
|
+
if (!coversUniqueKey(table, [ref.column])) errors.push({
|
|
197
|
+
path,
|
|
198
|
+
message: `identity column "${ref.table}.${ref.column}" is not covered by a unique key of "${table.name}" — pick a column with a unique constraint, or use a composite identity that is`
|
|
199
|
+
});
|
|
200
|
+
}
|
|
185
201
|
function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
186
202
|
const path = `${base}.projection.identity`;
|
|
187
203
|
const pk = primaryKeyColumns(baseTable);
|
|
188
|
-
|
|
204
|
+
const { identity } = projection;
|
|
205
|
+
if (identity.kind === "base_pk" && !identity.column) {
|
|
189
206
|
if (pk.length === 0) {
|
|
190
207
|
errors.push({
|
|
191
208
|
path,
|
|
@@ -200,9 +217,21 @@ function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
|
200
217
|
});
|
|
201
218
|
return;
|
|
202
219
|
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (identity.kind === "base_pk" && identity.column) {
|
|
223
|
+
checkSingleColumnIdentity(errors, resolver, path, {
|
|
224
|
+
table: BASE_REF,
|
|
225
|
+
column: identity.column
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (identity.kind === "column") {
|
|
230
|
+
checkSingleColumnIdentity(errors, resolver, path, identity.from);
|
|
231
|
+
return;
|
|
203
232
|
}
|
|
204
|
-
if (
|
|
205
|
-
for (const part of
|
|
233
|
+
if (identity.kind !== "composite") return;
|
|
234
|
+
for (const part of identity.parts) {
|
|
206
235
|
const table = resolver.tableFor(part.table);
|
|
207
236
|
const meta = table && columnMetaFor(table, part.column);
|
|
208
237
|
if (!meta) continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_projection = require("./projection-
|
|
1
|
+
const require_projection = require("./projection-DEwL-D8R.cjs");
|
|
2
2
|
//#region ../core/src/schema-meta.ts
|
|
3
3
|
/**
|
|
4
4
|
* Accessors over a discovered table's schema-v2 metadata.
|
|
@@ -137,7 +137,7 @@ function checkTablesExist(errors, projection, resolver, base) {
|
|
|
137
137
|
if (!hasRichMeta(table)) {
|
|
138
138
|
errors.push({
|
|
139
139
|
path,
|
|
140
|
-
message: `table "${name}"
|
|
140
|
+
message: `table "${name}" was discovered without column types or unique keys, which a projection needs to prove its id is unique. Redeploy or restart the app so it re-reports its schema (or run \`clivly push-schema\`); if the table is declared by hand as \`{ name, columns }\`, use \`discoverFromDrizzle\` so keys and types come with it.`
|
|
141
141
|
});
|
|
142
142
|
ok = false;
|
|
143
143
|
}
|
|
@@ -182,10 +182,27 @@ function checkRefs(errors, projection, resolver, base, basePk) {
|
|
|
182
182
|
for (const [field, binding] of Object.entries(projection.fieldBindings)) checkRef(errors, resolver, `${base}.projection.fieldBindings.${field}`, binding.from);
|
|
183
183
|
for (const ref of require_projection.identityRefs(projection.identity, basePk)) checkRef(errors, resolver, `${base}.projection.identity`, ref);
|
|
184
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Prove that a single-column identity (`column`, or `base_pk` with an
|
|
187
|
+
* explicit column) is actually unique.
|
|
188
|
+
*
|
|
189
|
+
* `coversUniqueKey` fails closed on absent metadata, same as every other
|
|
190
|
+
* check in this file: a column not provably covered by a unique key is
|
|
191
|
+
* rejected, never assumed safe.
|
|
192
|
+
*/
|
|
193
|
+
function checkSingleColumnIdentity(errors, resolver, path, ref) {
|
|
194
|
+
const table = resolver.tableFor(ref.table);
|
|
195
|
+
if (!table) return;
|
|
196
|
+
if (!coversUniqueKey(table, [ref.column])) errors.push({
|
|
197
|
+
path,
|
|
198
|
+
message: `identity column "${ref.table}.${ref.column}" is not covered by a unique key of "${table.name}" — pick a column with a unique constraint, or use a composite identity that is`
|
|
199
|
+
});
|
|
200
|
+
}
|
|
185
201
|
function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
186
202
|
const path = `${base}.projection.identity`;
|
|
187
203
|
const pk = primaryKeyColumns(baseTable);
|
|
188
|
-
|
|
204
|
+
const { identity } = projection;
|
|
205
|
+
if (identity.kind === "base_pk" && !identity.column) {
|
|
189
206
|
if (pk.length === 0) {
|
|
190
207
|
errors.push({
|
|
191
208
|
path,
|
|
@@ -200,9 +217,21 @@ function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
|
200
217
|
});
|
|
201
218
|
return;
|
|
202
219
|
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (identity.kind === "base_pk" && identity.column) {
|
|
223
|
+
checkSingleColumnIdentity(errors, resolver, path, {
|
|
224
|
+
table: require_projection.BASE_REF,
|
|
225
|
+
column: identity.column
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (identity.kind === "column") {
|
|
230
|
+
checkSingleColumnIdentity(errors, resolver, path, identity.from);
|
|
231
|
+
return;
|
|
203
232
|
}
|
|
204
|
-
if (
|
|
205
|
-
for (const part of
|
|
233
|
+
if (identity.kind !== "composite") return;
|
|
234
|
+
for (const part of identity.parts) {
|
|
206
235
|
const table = resolver.tableFor(part.table);
|
|
207
236
|
const meta = table && columnMetaFor(table, part.column);
|
|
208
237
|
if (!meta) continue;
|
package/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const require_entity_config = require("./entity-config-
|
|
3
|
+
const require_entity_config = require("./entity-config-CPM-Tp-d.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-BL9BCzyD.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");
|
|
@@ -1596,7 +1596,7 @@ function planInit(schema, overrides = {}) {
|
|
|
1596
1596
|
todos.push(...candidateTodos);
|
|
1597
1597
|
company = candidate;
|
|
1598
1598
|
if (require_internal_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.`);
|
|
1599
|
-
} 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.`);
|
|
1599
|
+
} 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, or build a derived object from it in Clivly (Integrations → Map as custom object) if the fields live in a joined table.`);
|
|
1600
1600
|
}
|
|
1601
1601
|
if (contactTable && contact) {
|
|
1602
1602
|
const scored = require_internal_entity_heuristics.scoreRelationships(toScoredTable(contactTable), schema.tables.map(toScoredTable));
|
|
@@ -1871,6 +1871,7 @@ function renderImports(plan) {
|
|
|
1871
1871
|
"import { createClivlySDK, defineClivlyConfig } from \"clivly/sdk\";",
|
|
1872
1872
|
"import { fromDrizzle } from \"clivly/drizzle\";",
|
|
1873
1873
|
"import { discoverFromDrizzle } from \"clivly/drizzle\";",
|
|
1874
|
+
"import { drizzleIntrospector } from \"clivly/drizzle\";",
|
|
1874
1875
|
`import * as schema from "${plan.importPath}";`
|
|
1875
1876
|
];
|
|
1876
1877
|
if (plan.db === null) {
|
|
@@ -1903,6 +1904,9 @@ export default createClivlySDK({
|
|
|
1903
1904
|
// Every table in your Drizzle schema, so the Clivly dashboard can offer them
|
|
1904
1905
|
// all when you add a mapping — not just the ones wired up as sources below.
|
|
1905
1906
|
schema: discoverFromDrizzle(schema),
|
|
1907
|
+
// Lets derived objects authored in the dashboard run against this database.
|
|
1908
|
+
// Without it they are fetched, validated and then skipped.
|
|
1909
|
+
introspector: drizzleIntrospector(${dbExpr}),
|
|
1906
1910
|
source: {
|
|
1907
1911
|
// Keyed by CRM concept — contacts/companies/deals/custom.
|
|
1908
1912
|
// NOT by your entity key. The entity's own \`source:\` above is a table name.
|
|
@@ -3124,15 +3128,16 @@ const triggerUrlStage = {
|
|
|
3124
3128
|
label: "Sync trigger URL",
|
|
3125
3129
|
needs: [configStage.id],
|
|
3126
3130
|
run(ctx) {
|
|
3127
|
-
const
|
|
3128
|
-
if (
|
|
3131
|
+
const trigger = ctx.sdk?.describeSyncTrigger?.();
|
|
3132
|
+
if (trigger?.resolved) return Promise.resolve({
|
|
3129
3133
|
state: "pass",
|
|
3130
|
-
detail:
|
|
3134
|
+
detail: trigger.resolved
|
|
3131
3135
|
});
|
|
3132
|
-
const
|
|
3136
|
+
const configured = trigger?.configured;
|
|
3137
|
+
const isPlaceholder = configured !== void 0 && require_sdk.isPlaceholderUrl(configured);
|
|
3133
3138
|
return Promise.resolve({
|
|
3134
3139
|
state: "warn",
|
|
3135
|
-
detail: isPlaceholder ? `Still the placeholder (${
|
|
3140
|
+
detail: isPlaceholder ? `Still the scaffolded placeholder (${configured}) — remote sync cannot call back into this app.` : "Not set — remote sync cannot call back into this app.",
|
|
3136
3141
|
hint: "Replace <yourDomain.com> with your deployed origin, or delete the line — on Vercel, Cloudflare Pages, Railway, Render and Fly the SDK derives the URL from the platform. For local development run `clivly dev`: it opens a tunnel and registers the URL for you, outranking whatever is in .env. Push-only setups can ignore this."
|
|
3137
3142
|
});
|
|
3138
3143
|
}
|
|
@@ -3200,6 +3205,32 @@ const STATUS_STAGES = [
|
|
|
3200
3205
|
}
|
|
3201
3206
|
},
|
|
3202
3207
|
sdkChecksStage,
|
|
3208
|
+
{
|
|
3209
|
+
id: "derived-objects",
|
|
3210
|
+
label: "Dashboard-authored objects",
|
|
3211
|
+
needs: [configStage.id],
|
|
3212
|
+
run(ctx) {
|
|
3213
|
+
const remote = ctx.sdk?.describeRemoteProjections?.();
|
|
3214
|
+
if (!remote) return Promise.resolve({
|
|
3215
|
+
state: "skip",
|
|
3216
|
+
detail: "This app's installed clivly predates dashboard-authored objects."
|
|
3217
|
+
});
|
|
3218
|
+
if (!remote.capable) return Promise.resolve({
|
|
3219
|
+
state: "warn",
|
|
3220
|
+
detail: "Objects authored in the dashboard cannot run against this app.",
|
|
3221
|
+
hint: "Add `introspector: drizzleIntrospector(db)` to clivly.config.ts."
|
|
3222
|
+
});
|
|
3223
|
+
if (!remote.ran) return Promise.resolve({
|
|
3224
|
+
state: "pass",
|
|
3225
|
+
detail: "Ready; no sync has run in this process yet."
|
|
3226
|
+
});
|
|
3227
|
+
const broken = remote.invalid + remote.failed;
|
|
3228
|
+
return Promise.resolve({
|
|
3229
|
+
state: broken > 0 ? "warn" : "pass",
|
|
3230
|
+
detail: broken > 0 ? `${remote.ok} running, ${broken} not — open the object in Clivly for the reason.` : `${remote.ok} running.`
|
|
3231
|
+
});
|
|
3232
|
+
}
|
|
3233
|
+
},
|
|
3203
3234
|
triggerUrlStage
|
|
3204
3235
|
];
|
|
3205
3236
|
function defaultLoadSdk(path) {
|
|
@@ -3253,7 +3284,8 @@ const ALIAS_STAGE_IDS = {
|
|
|
3253
3284
|
"config",
|
|
3254
3285
|
"runtime",
|
|
3255
3286
|
"boot-hook",
|
|
3256
|
-
"setup-checks"
|
|
3287
|
+
"setup-checks",
|
|
3288
|
+
"derived-objects"
|
|
3257
3289
|
],
|
|
3258
3290
|
connect: [
|
|
3259
3291
|
"api-key",
|
package/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as CANONICAL_FIELDS } from "./entity-config-
|
|
2
|
+
import { t as CANONICAL_FIELDS } from "./entity-config-C8IIkrmX.mjs";
|
|
3
3
|
import "./mapping-score-C68uAqGB.mjs";
|
|
4
4
|
import { matchesConcept, requiredFieldsFor, scoreFieldMap, scoreRelationships, suggestCursorField, toFieldMap } from "./internal-entity-heuristics.mjs";
|
|
5
|
-
import { s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-
|
|
5
|
+
import { s as isPlaceholderUrl, t as createClivlySDK } from "./sdk-CubnsyD9.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";
|
|
@@ -1595,7 +1595,7 @@ function planInit(schema, overrides = {}) {
|
|
|
1595
1595
|
todos.push(...candidateTodos);
|
|
1596
1596
|
company = candidate;
|
|
1597
1597
|
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.`);
|
|
1598
|
-
} 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.`);
|
|
1598
|
+
} 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, or build a derived object from it in Clivly (Integrations → Map as custom object) if the fields live in a joined table.`);
|
|
1599
1599
|
}
|
|
1600
1600
|
if (contactTable && contact) {
|
|
1601
1601
|
const scored = scoreRelationships(toScoredTable(contactTable), schema.tables.map(toScoredTable));
|
|
@@ -1870,6 +1870,7 @@ function renderImports(plan) {
|
|
|
1870
1870
|
"import { createClivlySDK, defineClivlyConfig } from \"clivly/sdk\";",
|
|
1871
1871
|
"import { fromDrizzle } from \"clivly/drizzle\";",
|
|
1872
1872
|
"import { discoverFromDrizzle } from \"clivly/drizzle\";",
|
|
1873
|
+
"import { drizzleIntrospector } from \"clivly/drizzle\";",
|
|
1873
1874
|
`import * as schema from "${plan.importPath}";`
|
|
1874
1875
|
];
|
|
1875
1876
|
if (plan.db === null) {
|
|
@@ -1902,6 +1903,9 @@ export default createClivlySDK({
|
|
|
1902
1903
|
// Every table in your Drizzle schema, so the Clivly dashboard can offer them
|
|
1903
1904
|
// all when you add a mapping — not just the ones wired up as sources below.
|
|
1904
1905
|
schema: discoverFromDrizzle(schema),
|
|
1906
|
+
// Lets derived objects authored in the dashboard run against this database.
|
|
1907
|
+
// Without it they are fetched, validated and then skipped.
|
|
1908
|
+
introspector: drizzleIntrospector(${dbExpr}),
|
|
1905
1909
|
source: {
|
|
1906
1910
|
// Keyed by CRM concept — contacts/companies/deals/custom.
|
|
1907
1911
|
// NOT by your entity key. The entity's own \`source:\` above is a table name.
|
|
@@ -3123,15 +3127,16 @@ const triggerUrlStage = {
|
|
|
3123
3127
|
label: "Sync trigger URL",
|
|
3124
3128
|
needs: [configStage.id],
|
|
3125
3129
|
run(ctx) {
|
|
3126
|
-
const
|
|
3127
|
-
if (
|
|
3130
|
+
const trigger = ctx.sdk?.describeSyncTrigger?.();
|
|
3131
|
+
if (trigger?.resolved) return Promise.resolve({
|
|
3128
3132
|
state: "pass",
|
|
3129
|
-
detail:
|
|
3133
|
+
detail: trigger.resolved
|
|
3130
3134
|
});
|
|
3131
|
-
const
|
|
3135
|
+
const configured = trigger?.configured;
|
|
3136
|
+
const isPlaceholder = configured !== void 0 && isPlaceholderUrl(configured);
|
|
3132
3137
|
return Promise.resolve({
|
|
3133
3138
|
state: "warn",
|
|
3134
|
-
detail: isPlaceholder ? `Still the placeholder (${
|
|
3139
|
+
detail: isPlaceholder ? `Still the scaffolded placeholder (${configured}) — remote sync cannot call back into this app.` : "Not set — remote sync cannot call back into this app.",
|
|
3135
3140
|
hint: "Replace <yourDomain.com> with your deployed origin, or delete the line — on Vercel, Cloudflare Pages, Railway, Render and Fly the SDK derives the URL from the platform. For local development run `clivly dev`: it opens a tunnel and registers the URL for you, outranking whatever is in .env. Push-only setups can ignore this."
|
|
3136
3141
|
});
|
|
3137
3142
|
}
|
|
@@ -3199,6 +3204,32 @@ const STATUS_STAGES = [
|
|
|
3199
3204
|
}
|
|
3200
3205
|
},
|
|
3201
3206
|
sdkChecksStage,
|
|
3207
|
+
{
|
|
3208
|
+
id: "derived-objects",
|
|
3209
|
+
label: "Dashboard-authored objects",
|
|
3210
|
+
needs: [configStage.id],
|
|
3211
|
+
run(ctx) {
|
|
3212
|
+
const remote = ctx.sdk?.describeRemoteProjections?.();
|
|
3213
|
+
if (!remote) return Promise.resolve({
|
|
3214
|
+
state: "skip",
|
|
3215
|
+
detail: "This app's installed clivly predates dashboard-authored objects."
|
|
3216
|
+
});
|
|
3217
|
+
if (!remote.capable) return Promise.resolve({
|
|
3218
|
+
state: "warn",
|
|
3219
|
+
detail: "Objects authored in the dashboard cannot run against this app.",
|
|
3220
|
+
hint: "Add `introspector: drizzleIntrospector(db)` to clivly.config.ts."
|
|
3221
|
+
});
|
|
3222
|
+
if (!remote.ran) return Promise.resolve({
|
|
3223
|
+
state: "pass",
|
|
3224
|
+
detail: "Ready; no sync has run in this process yet."
|
|
3225
|
+
});
|
|
3226
|
+
const broken = remote.invalid + remote.failed;
|
|
3227
|
+
return Promise.resolve({
|
|
3228
|
+
state: broken > 0 ? "warn" : "pass",
|
|
3229
|
+
detail: broken > 0 ? `${remote.ok} running, ${broken} not — open the object in Clivly for the reason.` : `${remote.ok} running.`
|
|
3230
|
+
});
|
|
3231
|
+
}
|
|
3232
|
+
},
|
|
3202
3233
|
triggerUrlStage
|
|
3203
3234
|
];
|
|
3204
3235
|
function defaultLoadSdk(path) {
|
|
@@ -3252,7 +3283,8 @@ const ALIAS_STAGE_IDS = {
|
|
|
3252
3283
|
"config",
|
|
3253
3284
|
"runtime",
|
|
3254
3285
|
"boot-hook",
|
|
3255
|
-
"setup-checks"
|
|
3286
|
+
"setup-checks",
|
|
3287
|
+
"derived-objects"
|
|
3256
3288
|
],
|
|
3257
3289
|
connect: [
|
|
3258
3290
|
"api-key",
|