@clivly/core 0.9.6 → 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 +13 -0
- package/dist/drizzle.cjs +9 -1
- package/dist/drizzle.d.cts +3 -1
- package/dist/drizzle.d.mts +3 -1
- package/dist/drizzle.mjs +9 -1
- package/dist/{entity-config-BAaZOIID.cjs → entity-config-25v_udeY.cjs} +33 -4
- package/dist/{entity-config-kusaRiDs.mjs → entity-config-DwGXbUo_.mjs} +33 -4
- package/dist/entity-config.cjs +1 -1
- package/dist/entity-config.mjs +1 -1
- package/dist/entity-heuristics.cjs +1 -1
- package/dist/entity-heuristics.mjs +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/mappings-config.cjs +1 -1
- package/dist/mappings-config.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -168,6 +168,19 @@ Refer to the base table as `base` and each join by its `key`. Validation is
|
|
|
168
168
|
`uniqueConstraints` is rejected rather than assumed safe, because identity
|
|
169
169
|
uniqueness cannot be proven without them.
|
|
170
170
|
|
|
171
|
+
`validateProjection` is also the SDK's guard when it runs an object authored in
|
|
172
|
+
Clivly's dashboard: the definition arrives over the network, and the host
|
|
173
|
+
revalidates it **against the schema the host itself discovered** before any SQL
|
|
174
|
+
is compiled. Because `checkTablesExist` and `checkRefs` bound every table and
|
|
175
|
+
column reference to the schema array you pass in, that call is what limits a
|
|
176
|
+
remote definition to the namespace the developer handed to `discoverFromDrizzle`
|
|
177
|
+
— pass a different schema and you move that boundary.
|
|
178
|
+
|
|
179
|
+
Since `0.9.7`, **every** identity kind must be provably unique, not just
|
|
180
|
+
composite ones: a single-column identity is rejected unless a unique key covers
|
|
181
|
+
it. The identity is what the sync pages by and what decides which rows survive a
|
|
182
|
+
snapshot, so an unproven one can silently skip and then archive real records.
|
|
183
|
+
|
|
171
184
|
### Fan-out
|
|
172
185
|
|
|
173
186
|
A join that does not cover a unique key of the joined table can match many rows
|
package/dist/drizzle.cjs
CHANGED
|
@@ -92,7 +92,7 @@ function buildColumnsMeta(table) {
|
|
|
92
92
|
* when you import this entry (`@clivly/core/drizzle`); the rest of `@clivly/core`
|
|
93
93
|
* stays dependency-free.
|
|
94
94
|
*/
|
|
95
|
-
function discoverFromDrizzle(schema) {
|
|
95
|
+
function discoverFromDrizzle(schema, options = {}) {
|
|
96
96
|
const tables = [];
|
|
97
97
|
for (const value of Object.values(schema)) {
|
|
98
98
|
if (!(0, drizzle_orm.is)(value, drizzle_orm.Table)) continue;
|
|
@@ -105,8 +105,16 @@ function discoverFromDrizzle(schema) {
|
|
|
105
105
|
...uniqueConstraints === void 0 ? {} : { uniqueConstraints }
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
+
if (tables.length === 0 && !options.allowEmpty) throw new Error(`discoverFromDrizzle: found no Drizzle tables in the object it was given (${describeKeys(schema)}). Import the schema module's tables directly — \`import * as schema from "./db/schema"\` — rather than a barrel or a default export, which can collapse to \`{ default }\` under ESM/CJS interop. Pass { allowEmpty: true } if an empty schema is genuinely intended.`);
|
|
108
109
|
return tables;
|
|
109
110
|
}
|
|
111
|
+
/** The keys actually present, so the message names what was handed over. */
|
|
112
|
+
function describeKeys(schema) {
|
|
113
|
+
const keys = Object.keys(schema);
|
|
114
|
+
if (keys.length === 0) return "it was empty";
|
|
115
|
+
const shown = keys.slice(0, 5).join(", ");
|
|
116
|
+
return keys.length > 5 ? `keys: ${shown}, +${keys.length - 5} more` : `keys: ${shown}`;
|
|
117
|
+
}
|
|
110
118
|
//#endregion
|
|
111
119
|
exports.buildColumnsMeta = buildColumnsMeta;
|
|
112
120
|
exports.buildUniqueConstraints = buildUniqueConstraints;
|
package/dist/drizzle.d.cts
CHANGED
|
@@ -36,6 +36,8 @@ declare function buildColumnsMeta(table: Table): DiscoveredColumnMeta[];
|
|
|
36
36
|
* when you import this entry (`@clivly/core/drizzle`); the rest of `@clivly/core`
|
|
37
37
|
* stays dependency-free.
|
|
38
38
|
*/
|
|
39
|
-
declare function discoverFromDrizzle(schema: Record<string, unknown
|
|
39
|
+
declare function discoverFromDrizzle(schema: Record<string, unknown>, options?: {
|
|
40
|
+
allowEmpty?: boolean;
|
|
41
|
+
}): DiscoveredSchemaTable[];
|
|
40
42
|
//#endregion
|
|
41
43
|
export { buildColumnsMeta, buildUniqueConstraints, discoverFromDrizzle };
|
package/dist/drizzle.d.mts
CHANGED
|
@@ -36,6 +36,8 @@ declare function buildColumnsMeta(table: Table): DiscoveredColumnMeta[];
|
|
|
36
36
|
* when you import this entry (`@clivly/core/drizzle`); the rest of `@clivly/core`
|
|
37
37
|
* stays dependency-free.
|
|
38
38
|
*/
|
|
39
|
-
declare function discoverFromDrizzle(schema: Record<string, unknown
|
|
39
|
+
declare function discoverFromDrizzle(schema: Record<string, unknown>, options?: {
|
|
40
|
+
allowEmpty?: boolean;
|
|
41
|
+
}): DiscoveredSchemaTable[];
|
|
40
42
|
//#endregion
|
|
41
43
|
export { buildColumnsMeta, buildUniqueConstraints, discoverFromDrizzle };
|
package/dist/drizzle.mjs
CHANGED
|
@@ -91,7 +91,7 @@ function buildColumnsMeta(table) {
|
|
|
91
91
|
* when you import this entry (`@clivly/core/drizzle`); the rest of `@clivly/core`
|
|
92
92
|
* stays dependency-free.
|
|
93
93
|
*/
|
|
94
|
-
function discoverFromDrizzle(schema) {
|
|
94
|
+
function discoverFromDrizzle(schema, options = {}) {
|
|
95
95
|
const tables = [];
|
|
96
96
|
for (const value of Object.values(schema)) {
|
|
97
97
|
if (!is(value, Table)) continue;
|
|
@@ -104,7 +104,15 @@ function discoverFromDrizzle(schema) {
|
|
|
104
104
|
...uniqueConstraints === void 0 ? {} : { uniqueConstraints }
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
|
+
if (tables.length === 0 && !options.allowEmpty) throw new Error(`discoverFromDrizzle: found no Drizzle tables in the object it was given (${describeKeys(schema)}). Import the schema module's tables directly — \`import * as schema from "./db/schema"\` — rather than a barrel or a default export, which can collapse to \`{ default }\` under ESM/CJS interop. Pass { allowEmpty: true } if an empty schema is genuinely intended.`);
|
|
107
108
|
return tables;
|
|
108
109
|
}
|
|
110
|
+
/** The keys actually present, so the message names what was handed over. */
|
|
111
|
+
function describeKeys(schema) {
|
|
112
|
+
const keys = Object.keys(schema);
|
|
113
|
+
if (keys.length === 0) return "it was empty";
|
|
114
|
+
const shown = keys.slice(0, 5).join(", ");
|
|
115
|
+
return keys.length > 5 ? `keys: ${shown}, +${keys.length - 5} more` : `keys: ${shown}`;
|
|
116
|
+
}
|
|
109
117
|
//#endregion
|
|
110
118
|
export { buildColumnsMeta, buildUniqueConstraints, discoverFromDrizzle };
|
|
@@ -138,7 +138,7 @@ function checkTablesExist(errors, projection, resolver, base) {
|
|
|
138
138
|
if (!hasRichMeta(table)) {
|
|
139
139
|
errors.push({
|
|
140
140
|
path,
|
|
141
|
-
message: `table "${name}"
|
|
141
|
+
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.`
|
|
142
142
|
});
|
|
143
143
|
ok = false;
|
|
144
144
|
}
|
|
@@ -183,10 +183,27 @@ function checkRefs(errors, projection, resolver, base, basePk) {
|
|
|
183
183
|
for (const [field, binding] of Object.entries(projection.fieldBindings)) checkRef(errors, resolver, `${base}.projection.fieldBindings.${field}`, binding.from);
|
|
184
184
|
for (const ref of require_projection.identityRefs(projection.identity, basePk)) checkRef(errors, resolver, `${base}.projection.identity`, ref);
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Prove that a single-column identity (`column`, or `base_pk` with an
|
|
188
|
+
* explicit column) is actually unique.
|
|
189
|
+
*
|
|
190
|
+
* `coversUniqueKey` fails closed on absent metadata, same as every other
|
|
191
|
+
* check in this file: a column not provably covered by a unique key is
|
|
192
|
+
* rejected, never assumed safe.
|
|
193
|
+
*/
|
|
194
|
+
function checkSingleColumnIdentity(errors, resolver, path, ref) {
|
|
195
|
+
const table = resolver.tableFor(ref.table);
|
|
196
|
+
if (!table) return;
|
|
197
|
+
if (!coversUniqueKey(table, [ref.column])) errors.push({
|
|
198
|
+
path,
|
|
199
|
+
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`
|
|
200
|
+
});
|
|
201
|
+
}
|
|
186
202
|
function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
187
203
|
const path = `${base}.projection.identity`;
|
|
188
204
|
const pk = primaryKeyColumns(baseTable);
|
|
189
|
-
|
|
205
|
+
const { identity } = projection;
|
|
206
|
+
if (identity.kind === "base_pk" && !identity.column) {
|
|
190
207
|
if (pk.length === 0) {
|
|
191
208
|
errors.push({
|
|
192
209
|
path,
|
|
@@ -201,9 +218,21 @@ function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
|
201
218
|
});
|
|
202
219
|
return;
|
|
203
220
|
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (identity.kind === "base_pk" && identity.column) {
|
|
224
|
+
checkSingleColumnIdentity(errors, resolver, path, {
|
|
225
|
+
table: require_projection.BASE_REF,
|
|
226
|
+
column: identity.column
|
|
227
|
+
});
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (identity.kind === "column") {
|
|
231
|
+
checkSingleColumnIdentity(errors, resolver, path, identity.from);
|
|
232
|
+
return;
|
|
204
233
|
}
|
|
205
|
-
if (
|
|
206
|
-
for (const part of
|
|
234
|
+
if (identity.kind !== "composite") return;
|
|
235
|
+
for (const part of identity.parts) {
|
|
207
236
|
const table = resolver.tableFor(part.table);
|
|
208
237
|
const meta = table && columnMetaFor(table, part.column);
|
|
209
238
|
if (!meta) continue;
|
|
@@ -138,7 +138,7 @@ function checkTablesExist(errors, projection, resolver, base) {
|
|
|
138
138
|
if (!hasRichMeta(table)) {
|
|
139
139
|
errors.push({
|
|
140
140
|
path,
|
|
141
|
-
message: `table "${name}"
|
|
141
|
+
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.`
|
|
142
142
|
});
|
|
143
143
|
ok = false;
|
|
144
144
|
}
|
|
@@ -183,10 +183,27 @@ function checkRefs(errors, projection, resolver, base, basePk) {
|
|
|
183
183
|
for (const [field, binding] of Object.entries(projection.fieldBindings)) checkRef(errors, resolver, `${base}.projection.fieldBindings.${field}`, binding.from);
|
|
184
184
|
for (const ref of identityRefs(projection.identity, basePk)) checkRef(errors, resolver, `${base}.projection.identity`, ref);
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Prove that a single-column identity (`column`, or `base_pk` with an
|
|
188
|
+
* explicit column) is actually unique.
|
|
189
|
+
*
|
|
190
|
+
* `coversUniqueKey` fails closed on absent metadata, same as every other
|
|
191
|
+
* check in this file: a column not provably covered by a unique key is
|
|
192
|
+
* rejected, never assumed safe.
|
|
193
|
+
*/
|
|
194
|
+
function checkSingleColumnIdentity(errors, resolver, path, ref) {
|
|
195
|
+
const table = resolver.tableFor(ref.table);
|
|
196
|
+
if (!table) return;
|
|
197
|
+
if (!coversUniqueKey(table, [ref.column])) errors.push({
|
|
198
|
+
path,
|
|
199
|
+
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`
|
|
200
|
+
});
|
|
201
|
+
}
|
|
186
202
|
function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
187
203
|
const path = `${base}.projection.identity`;
|
|
188
204
|
const pk = primaryKeyColumns(baseTable);
|
|
189
|
-
|
|
205
|
+
const { identity } = projection;
|
|
206
|
+
if (identity.kind === "base_pk" && !identity.column) {
|
|
190
207
|
if (pk.length === 0) {
|
|
191
208
|
errors.push({
|
|
192
209
|
path,
|
|
@@ -201,9 +218,21 @@ function checkIdentitySafety(errors, projection, resolver, base, baseTable) {
|
|
|
201
218
|
});
|
|
202
219
|
return;
|
|
203
220
|
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (identity.kind === "base_pk" && identity.column) {
|
|
224
|
+
checkSingleColumnIdentity(errors, resolver, path, {
|
|
225
|
+
table: BASE_REF,
|
|
226
|
+
column: identity.column
|
|
227
|
+
});
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (identity.kind === "column") {
|
|
231
|
+
checkSingleColumnIdentity(errors, resolver, path, identity.from);
|
|
232
|
+
return;
|
|
204
233
|
}
|
|
205
|
-
if (
|
|
206
|
-
for (const part of
|
|
234
|
+
if (identity.kind !== "composite") return;
|
|
235
|
+
for (const part of identity.parts) {
|
|
207
236
|
const table = resolver.tableFor(part.table);
|
|
208
237
|
const meta = table && columnMetaFor(table, part.column);
|
|
209
238
|
if (!meta) continue;
|
package/dist/entity-config.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_entity_config = require("./entity-config-
|
|
2
|
+
const require_entity_config = require("./entity-config-25v_udeY.cjs");
|
|
3
3
|
exports.CANONICAL_FIELDS = require_entity_config.CANONICAL_FIELDS;
|
|
4
4
|
exports.CRM_CONCEPTS = require_entity_config.CRM_CONCEPTS;
|
|
5
5
|
exports.CUSTOM_CONCEPT = require_entity_config.CUSTOM_CONCEPT;
|
package/dist/entity-config.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as MATERIALIZING_CONCEPTS, c as entitiesConfigSchema, d as relationshipSchema, f as relationshipsSchema, i as ClivlyConfigError, l as filterSchema, n as CRM_CONCEPTS, o as customObjectDeclarations, p as validateEntitiesConfig, r as CUSTOM_CONCEPT, s as defineClivlyConfig, t as CANONICAL_FIELDS, u as projectionFields } from "./entity-config-
|
|
1
|
+
import { a as MATERIALIZING_CONCEPTS, c as entitiesConfigSchema, d as relationshipSchema, f as relationshipsSchema, i as ClivlyConfigError, l as filterSchema, n as CRM_CONCEPTS, o as customObjectDeclarations, p as validateEntitiesConfig, r as CUSTOM_CONCEPT, s as defineClivlyConfig, t as CANONICAL_FIELDS, u as projectionFields } from "./entity-config-DwGXbUo_.mjs";
|
|
2
2
|
export { CANONICAL_FIELDS, CRM_CONCEPTS, CUSTOM_CONCEPT, ClivlyConfigError, MATERIALIZING_CONCEPTS, customObjectDeclarations, defineClivlyConfig, entitiesConfigSchema, filterSchema, projectionFields, relationshipSchema, relationshipsSchema, validateEntitiesConfig };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_entity_config = require("./entity-config-
|
|
2
|
+
const require_entity_config = require("./entity-config-25v_udeY.cjs");
|
|
3
3
|
const require_mapping_score = require("./mapping-score.cjs");
|
|
4
4
|
//#region src/entity-heuristics.ts
|
|
5
5
|
const TARGET_CONCEPTS = [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as MATERIALIZING_CONCEPTS } from "./entity-config-
|
|
1
|
+
import { a as MATERIALIZING_CONCEPTS } from "./entity-config-DwGXbUo_.mjs";
|
|
2
2
|
import { CONCEPT_TIERS, FIELD_TIERS, RELATIONSHIP_TIERS, W, isAmbiguous, isNumericLike, isTextLike, isTimestampLike, tierFor } from "./mapping-score.mjs";
|
|
3
3
|
//#region src/entity-heuristics.ts
|
|
4
4
|
const TARGET_CONCEPTS = [
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_auth_adapter = require("./auth-adapter.cjs");
|
|
3
3
|
const require_projection = require("./projection-OJYCYmS_.cjs");
|
|
4
|
-
const require_entity_config = require("./entity-config-
|
|
4
|
+
const require_entity_config = require("./entity-config-25v_udeY.cjs");
|
|
5
5
|
const require_mapping_form = require("./mapping-form.cjs");
|
|
6
6
|
const require_mappings_config = require("./mappings-config.cjs");
|
|
7
7
|
const require_view_compiler = require("./view-compiler-D30HNRnA.cjs");
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { authCustom } from "./auth-adapter.mjs";
|
|
2
2
|
import { a as projectionSchema, i as identityRefs, n as PROJECTION_KEY_MESSAGE, r as PROJECTION_KEY_PATTERN, t as BASE_REF } from "./projection-BXw_iiw5.mjs";
|
|
3
|
-
import { _ as columnMetaFor, a as MATERIALIZING_CONCEPTS, b as primaryKeyColumns, c as entitiesConfigSchema, d as relationshipSchema, f as relationshipsSchema, g as joinFansOut, h as fanOutJoins, i as ClivlyConfigError, l as filterSchema, m as validateProjection, n as CRM_CONCEPTS, p as validateEntitiesConfig, s as defineClivlyConfig, t as CANONICAL_FIELDS, v as coversUniqueKey, y as hasRichMeta } from "./entity-config-
|
|
3
|
+
import { _ as columnMetaFor, a as MATERIALIZING_CONCEPTS, b as primaryKeyColumns, c as entitiesConfigSchema, d as relationshipSchema, f as relationshipsSchema, g as joinFansOut, h as fanOutJoins, i as ClivlyConfigError, l as filterSchema, m as validateProjection, n as CRM_CONCEPTS, p as validateEntitiesConfig, s as defineClivlyConfig, t as CANONICAL_FIELDS, v as coversUniqueKey, y as hasRichMeta } from "./entity-config-DwGXbUo_.mjs";
|
|
4
4
|
import { buildFilterFromDiscriminator, buildRelationships, relationshipViaSignature } from "./mapping-form.mjs";
|
|
5
5
|
import { mappingsToEntitiesConfig } from "./mappings-config.mjs";
|
|
6
6
|
import { c as compileProjectionIdentity, d as compileProjectionSelect, f as quoteIdent, i as explainEntitiesConfig, l as compileProjectionJoins, n as compileEntityView, o as PREVIEW_MAX_ROWS, r as compileEntityViews, s as compileProjectionFrom, t as DEFAULT_VIEW_PREFIX, u as compileProjectionPreview } from "./view-compiler-u3GGRgnv.mjs";
|
package/dist/mappings-config.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_entity_config = require("./entity-config-
|
|
2
|
+
const require_entity_config = require("./entity-config-25v_udeY.cjs");
|
|
3
3
|
//#region src/mappings-config.ts
|
|
4
4
|
const RESERVED_FIELD = "id";
|
|
5
5
|
/**
|
package/dist/mappings-config.mjs
CHANGED