clivly 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.
Files changed (58) hide show
  1. package/README.md +15 -3
  2. package/core-auth-adapter.cjs +1 -1
  3. package/core-auth-adapter.mjs +1 -1
  4. package/core-drizzle.cjs +1 -1
  5. package/core-drizzle.d.cts +1 -1
  6. package/core-drizzle.d.mts +1 -1
  7. package/core-drizzle.mjs +1 -1
  8. package/core-entity-config.cjs +1 -1
  9. package/core-entity-config.mjs +1 -1
  10. package/core-mapping-form.cjs +1 -1
  11. package/core-mapping-form.mjs +1 -1
  12. package/core-mappings-config.cjs +1 -1
  13. package/core-mappings-config.mjs +1 -1
  14. package/core-sync-engine.cjs +1 -1
  15. package/core-sync-engine.mjs +1 -1
  16. package/core-view-compiler.cjs +1 -1
  17. package/core-view-compiler.mjs +1 -1
  18. package/core.cjs +4 -5
  19. package/core.mjs +4 -4
  20. package/{drizzle-CiK8MMEZ.d.mts → drizzle-BmDDgAIF.d.mts} +3 -1
  21. package/{drizzle-bfjVzYR6.cjs → drizzle-DcB7OfBp.cjs} +9 -1
  22. package/{drizzle-DEtRrqtc.d.cts → drizzle-ixVV4pPN.d.cts} +3 -1
  23. package/{drizzle-3kvhkwO7.mjs → drizzle-wbcuG-cy.mjs} +9 -1
  24. package/drizzle.cjs +56 -22
  25. package/drizzle.d.cts +9 -3
  26. package/drizzle.d.mts +9 -3
  27. package/drizzle.mjs +56 -22
  28. package/{entity-config-CusbTOJ1.mjs → entity-config-C8IIkrmX.mjs} +33 -4
  29. package/{entity-config-DE30_RpU.cjs → entity-config-CPM-Tp-d.cjs} +33 -4
  30. package/index.cjs +34 -3
  31. package/index.mjs +34 -3
  32. package/{internal-BQ47iN6f.cjs → internal-BLKMnIIZ.cjs} +1 -1
  33. package/{internal-ChufvXE6.mjs → internal-BpVh_J77.mjs} +1 -1
  34. package/internal-drizzle.cjs +1 -1
  35. package/internal-drizzle.d.cts +1 -1
  36. package/internal-drizzle.d.mts +1 -1
  37. package/internal-drizzle.mjs +1 -1
  38. package/internal-entity-config.cjs +1 -1
  39. package/internal-entity-config.mjs +1 -1
  40. package/internal-entity-heuristics.cjs +1 -1
  41. package/internal-entity-heuristics.mjs +1 -1
  42. package/internal-mappings-config.cjs +1 -1
  43. package/internal-mappings-config.mjs +1 -1
  44. package/internal.cjs +3 -3
  45. package/internal.mjs +3 -3
  46. package/{mappings-config-CjCmT-aU.cjs → mappings-config-CBwL3FVh.cjs} +1 -1
  47. package/{mappings-config-7qQ47p9K.mjs → mappings-config-DUKBe6OS.mjs} +1 -1
  48. package/{namespace-probe-Ckn9yJ6F.d.mts → namespace-probe-BQ4imnV7.d.mts} +41 -1
  49. package/{namespace-probe-DaTJINS-.d.cts → namespace-probe-DZIGdvlm.d.cts} +41 -1
  50. package/package.json +1 -1
  51. package/{sdk-w8Kv_XnW.cjs → sdk-BL9BCzyD.cjs} +324 -14
  52. package/{sdk-Dkq6ELkf.mjs → sdk-CubnsyD9.mjs} +324 -14
  53. package/sdk.cjs +2 -2
  54. package/sdk.d.cts +2 -2
  55. package/sdk.d.mts +2 -2
  56. package/sdk.mjs +2 -2
  57. package/vite.d.cts +1 -1
  58. package/vite.d.mts +1 -1
package/README.md CHANGED
@@ -92,6 +92,12 @@ empty, for you or `clivly login` to fill; the trigger URL gets a visible
92
92
  `https://<yourDomain.com>/api/clivly/tick` template, because there the shape is
93
93
  the documentation.
94
94
 
95
+ From `0.9.7` the scaffolded config also sets `introspector:
96
+ drizzleIntrospector(db)`. Clivly never connects to your database, so that handle
97
+ is what lets your app run a **derived object** authored in Clivly's dashboard —
98
+ scaffolding it by default is what makes such an object sync with no further code
99
+ on your side. It costs nothing if you never author one.
100
+
95
101
  | Flag | Purpose |
96
102
  | --- | --- |
97
103
  | `--dry-run` | Show what would be written; write nothing |
@@ -268,7 +274,7 @@ It answers two questions under two headings:
268
274
  scope; `clivly doctor` is local by definition and omits it.
269
275
  - **This project** — the local ladder, run here on your machine: credential →
270
276
  Clivly reachable → `clivly.config` loads → `clivly.config` is edge-safe →
271
- boot hook wired → setup checks → sync trigger URL.
277
+ boot hook wired → setup checks → dashboard-authored objects → sync trigger URL.
272
278
 
273
279
  ```text
274
280
  Clivly status
@@ -281,11 +287,17 @@ Workspace
281
287
  🔒 Let Clivly start a sync
282
288
 
283
289
  This project
284
- ✅ Step 1 of 7 · API key present
285
- ✅ Step 2 of 7 · Clivly reachable — Acme
290
+ ✅ Step 1 of 8 · API key present
291
+ ✅ Step 2 of 8 · Clivly reachable — Acme
286
292
  ...
287
293
  ```
288
294
 
295
+ **Dashboard-authored objects** is new in `0.9.7`. It reports how many derived
296
+ objects authored in Clivly ran on the last sync and how many were quarantined —
297
+ and, when your config has no `introspector`, warns that such objects cannot run
298
+ at all here and names the one line to add. An app whose installed `clivly`
299
+ predates the feature reports nothing rather than a failure.
300
+
289
301
  **Boot hook wired** is the rung to know about. It finds the boot site for your
290
302
  framework and confirms something there actually starts Clivly — and on Next.js
291
303
  below 15, that `experimental.instrumentationHook` is set in `next.config`,
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_deprecate_subpath = require("./deprecate-subpath-B9ngvizt.cjs");
3
2
  const require_auth_adapter = require("./auth-adapter-CiEXcweZ.cjs");
3
+ const require_deprecate_subpath = require("./deprecate-subpath-B9ngvizt.cjs");
4
4
  require("./internal-auth-adapter.cjs");
5
5
  //#region src/core-auth-adapter.ts
6
6
  require_deprecate_subpath.warnDeprecatedSubpath("clivly/core/auth-adapter", "clivly/internal/auth-adapter");
@@ -1,5 +1,5 @@
1
- import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
2
1
  import { t as authCustom } from "./auth-adapter-gfVDxf3d.mjs";
2
+ import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
3
3
  import "./internal-auth-adapter.mjs";
4
4
  //#region src/core-auth-adapter.ts
5
5
  warnDeprecatedSubpath("clivly/core/auth-adapter", "clivly/internal/auth-adapter");
package/core-drizzle.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_deprecate_subpath = require("./deprecate-subpath-B9ngvizt.cjs");
3
- const require_drizzle = require("./drizzle-bfjVzYR6.cjs");
3
+ const require_drizzle = require("./drizzle-DcB7OfBp.cjs");
4
4
  require("./internal-drizzle.cjs");
5
5
  //#region src/core-drizzle.ts
6
6
  require_deprecate_subpath.warnDeprecatedSubpath("clivly/core/drizzle", "clivly/internal/drizzle");
@@ -1,3 +1,3 @@
1
- import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-DEtRrqtc.cjs";
1
+ import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-ixVV4pPN.cjs";
2
2
  import "./internal-drizzle.cjs";
3
3
  export { buildColumnsMeta, buildUniqueConstraints, discoverFromDrizzle };
@@ -1,3 +1,3 @@
1
- import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-CiK8MMEZ.mjs";
1
+ import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-BmDDgAIF.mjs";
2
2
  import "./internal-drizzle.mjs";
3
3
  export { buildColumnsMeta, buildUniqueConstraints, discoverFromDrizzle };
package/core-drizzle.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
2
- import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-3kvhkwO7.mjs";
2
+ import { n as buildUniqueConstraints, r as discoverFromDrizzle, t as buildColumnsMeta } from "./drizzle-wbcuG-cy.mjs";
3
3
  import "./internal-drizzle.mjs";
4
4
  //#region src/core-drizzle.ts
5
5
  warnDeprecatedSubpath("clivly/core/drizzle", "clivly/internal/drizzle");
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_entity_config = require("./entity-config-DE30_RpU.cjs");
2
+ const require_entity_config = require("./entity-config-CPM-Tp-d.cjs");
3
3
  //#region src/core-entity-config.ts
4
4
  require("./deprecate-subpath-B9ngvizt.cjs").warnDeprecatedSubpath("clivly/core/entity-config", "clivly/internal/entity-config");
5
5
  //#endregion
@@ -1,4 +1,4 @@
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-CusbTOJ1.mjs";
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-C8IIkrmX.mjs";
2
2
  import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
3
3
  //#region src/core-entity-config.ts
4
4
  warnDeprecatedSubpath("clivly/core/entity-config", "clivly/internal/entity-config");
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_deprecate_subpath = require("./deprecate-subpath-B9ngvizt.cjs");
3
2
  const require_mapping_form = require("./mapping-form-BpfON_ME.cjs");
3
+ const require_deprecate_subpath = require("./deprecate-subpath-B9ngvizt.cjs");
4
4
  require("./internal-mapping-form.cjs");
5
5
  //#region src/core-mapping-form.ts
6
6
  require_deprecate_subpath.warnDeprecatedSubpath("clivly/core/mapping-form", "clivly/internal/mapping-form");
@@ -1,5 +1,5 @@
1
- import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
2
1
  import { n as buildRelationships, r as relationshipViaSignature, t as buildFilterFromDiscriminator } from "./mapping-form-DigkuvFc.mjs";
2
+ import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
3
3
  import "./internal-mapping-form.mjs";
4
4
  //#region src/core-mapping-form.ts
5
5
  warnDeprecatedSubpath("clivly/core/mapping-form", "clivly/internal/mapping-form");
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_mappings_config = require("./mappings-config-CjCmT-aU.cjs");
2
+ const require_mappings_config = require("./mappings-config-CBwL3FVh.cjs");
3
3
  const require_deprecate_subpath = require("./deprecate-subpath-B9ngvizt.cjs");
4
4
  require("./internal-mappings-config.cjs");
5
5
  //#region src/core-mappings-config.ts
@@ -1,4 +1,4 @@
1
- import { n as mappingsToEntitiesConfig, t as entitiesToMappings } from "./mappings-config-7qQ47p9K.mjs";
1
+ import { n as mappingsToEntitiesConfig, t as entitiesToMappings } from "./mappings-config-DUKBe6OS.mjs";
2
2
  import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
3
3
  import "./internal-mappings-config.mjs";
4
4
  //#region src/core-mappings-config.ts
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_deprecate_subpath = require("./deprecate-subpath-B9ngvizt.cjs");
3
2
  const require_sync_engine = require("./sync-engine-BLgwLpR6.cjs");
3
+ const require_deprecate_subpath = require("./deprecate-subpath-B9ngvizt.cjs");
4
4
  require("./internal-sync-engine.cjs");
5
5
  //#region src/core-sync-engine.ts
6
6
  require_deprecate_subpath.warnDeprecatedSubpath("clivly/core/sync-engine", "clivly/internal/sync-engine");
@@ -1,5 +1,5 @@
1
- import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
2
1
  import { i as runSync, n as previewSync, r as reconcile, t as computeSampleWarnings } from "./sync-engine-B_IzoUSx.mjs";
2
+ import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
3
3
  import "./internal-sync-engine.mjs";
4
4
  //#region src/core-sync-engine.ts
5
5
  warnDeprecatedSubpath("clivly/core/sync-engine", "clivly/internal/sync-engine");
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_deprecate_subpath = require("./deprecate-subpath-B9ngvizt.cjs");
3
2
  const require_view_compiler = require("./view-compiler-BHI5E4QI.cjs");
3
+ const require_deprecate_subpath = require("./deprecate-subpath-B9ngvizt.cjs");
4
4
  require("./internal-view-compiler.cjs");
5
5
  //#region src/core-view-compiler.ts
6
6
  require_deprecate_subpath.warnDeprecatedSubpath("clivly/core/view-compiler", "clivly/internal/view-compiler");
@@ -1,5 +1,5 @@
1
- import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
2
1
  import { a as explainViewExecutionError, i as explainEntitiesConfig, n as compileEntityView, r as compileEntityViews, t as DEFAULT_VIEW_PREFIX } from "./view-compiler-D_6gfXFr.mjs";
2
+ import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
3
3
  import "./internal-view-compiler.mjs";
4
4
  //#region src/core-view-compiler.ts
5
5
  warnDeprecatedSubpath("clivly/core/view-compiler", "clivly/internal/view-compiler");
package/core.cjs CHANGED
@@ -1,16 +1,15 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_projection = require("./projection-DEwL-D8R.cjs");
3
- const require_entity_config = require("./entity-config-DE30_RpU.cjs");
4
- const require_mappings_config = require("./mappings-config-CjCmT-aU.cjs");
5
- const require_deprecate_subpath = require("./deprecate-subpath-B9ngvizt.cjs");
3
+ const require_entity_config = require("./entity-config-CPM-Tp-d.cjs");
4
+ const require_mappings_config = require("./mappings-config-CBwL3FVh.cjs");
6
5
  const require_auth_adapter = require("./auth-adapter-CiEXcweZ.cjs");
7
6
  const require_mapping_form = require("./mapping-form-BpfON_ME.cjs");
8
- const require_internal = require("./internal-BQ47iN6f.cjs");
7
+ const require_internal = require("./internal-BLKMnIIZ.cjs");
9
8
  const require_projection_sql = require("./projection-sql-iHy-Cx6V.cjs");
10
9
  const require_view_compiler = require("./view-compiler-BHI5E4QI.cjs");
11
10
  const require_sync_engine = require("./sync-engine-BLgwLpR6.cjs");
12
11
  //#region src/core.ts
13
- require_deprecate_subpath.warnDeprecatedSubpath("clivly/core", "clivly/sdk", "Import \"defineClivlyConfig\" and its config types from \"clivly/sdk\"; import everything else from \"clivly/internal\" (no stability guarantee — see STABILITY.md).");
12
+ require("./deprecate-subpath-B9ngvizt.cjs").warnDeprecatedSubpath("clivly/core", "clivly/sdk", "Import \"defineClivlyConfig\" and its config types from \"clivly/sdk\"; import everything else from \"clivly/internal\" (no stability guarantee — see STABILITY.md).");
14
13
  //#endregion
15
14
  exports.BASE_REF = require_projection.BASE_REF;
16
15
  exports.CANONICAL_FIELDS = require_entity_config.CANONICAL_FIELDS;
package/core.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  import { a as projectionSchema, i as identityRefs, t as BASE_REF } from "./projection-hnPSgH6G.mjs";
2
- 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-CusbTOJ1.mjs";
3
- import { n as mappingsToEntitiesConfig } from "./mappings-config-7qQ47p9K.mjs";
4
- import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
2
+ 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-C8IIkrmX.mjs";
3
+ import { n as mappingsToEntitiesConfig } from "./mappings-config-DUKBe6OS.mjs";
5
4
  import { t as authCustom } from "./auth-adapter-gfVDxf3d.mjs";
6
5
  import { n as buildRelationships, r as relationshipViaSignature, t as buildFilterFromDiscriminator } from "./mapping-form-DigkuvFc.mjs";
7
- import { a as draftToProjection, c as suggestJoinConditions, i as draftRefOptions, n as describeCardinality, o as emptyProjectionDraft, r as draftErrors, s as removeDraftJoin, t as addDraftJoin } from "./internal-ChufvXE6.mjs";
6
+ import { a as draftToProjection, c as suggestJoinConditions, i as draftRefOptions, n as describeCardinality, o as emptyProjectionDraft, r as draftErrors, s as removeDraftJoin, t as addDraftJoin } from "./internal-BpVh_J77.mjs";
8
7
  import { a as compileProjectionPreview, i as compileProjectionJoins, n as compileProjectionFrom, o as compileProjectionSelect, r as compileProjectionIdentity, s as quoteIdent, t as PREVIEW_MAX_ROWS } from "./projection-sql-9zzTVGvf.mjs";
9
8
  import { i as explainEntitiesConfig, n as compileEntityView, r as compileEntityViews, t as DEFAULT_VIEW_PREFIX } from "./view-compiler-D_6gfXFr.mjs";
10
9
  import { i as runSync, r as reconcile } from "./sync-engine-B_IzoUSx.mjs";
10
+ import { t as warnDeprecatedSubpath } from "./deprecate-subpath-CmvZtCpE.mjs";
11
11
  //#region src/core.ts
12
12
  warnDeprecatedSubpath("clivly/core", "clivly/sdk", "Import \"defineClivlyConfig\" and its config types from \"clivly/sdk\"; import everything else from \"clivly/internal\" (no stability guarantee — see STABILITY.md).");
13
13
  //#endregion
@@ -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>): DiscoveredSchemaTable[];
39
+ declare function discoverFromDrizzle(schema: Record<string, unknown>, options?: {
40
+ allowEmpty?: boolean;
41
+ }): DiscoveredSchemaTable[];
40
42
  //#endregion
41
43
  export { buildUniqueConstraints as n, discoverFromDrizzle as r, buildColumnsMeta as t };
@@ -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 (!(0, drizzle_orm.is)(value, drizzle_orm.Table)) continue;
@@ -104,8 +104,16 @@ 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
  Object.defineProperty(exports, "buildColumnsMeta", {
111
119
  enumerable: true,
@@ -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>): DiscoveredSchemaTable[];
39
+ declare function discoverFromDrizzle(schema: Record<string, unknown>, options?: {
40
+ allowEmpty?: boolean;
41
+ }): DiscoveredSchemaTable[];
40
42
  //#endregion
41
43
  export { buildUniqueConstraints as n, discoverFromDrizzle as r, buildColumnsMeta as t };
@@ -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 { buildUniqueConstraints as n, discoverFromDrizzle as r, buildColumnsMeta as t };
package/drizzle.cjs CHANGED
@@ -2,22 +2,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_projection = require("./projection-DEwL-D8R.cjs");
3
3
  const require_drizzle_meta = require("./drizzle-meta-Bb1JWYqf.cjs");
4
4
  const require_projection_sql = require("./projection-sql-iHy-Cx6V.cjs");
5
- const require_drizzle = require("./drizzle-bfjVzYR6.cjs");
5
+ const require_drizzle = require("./drizzle-DcB7OfBp.cjs");
6
6
  let drizzle_orm = require("drizzle-orm");
7
- //#region ../sdk/src/projection-preview.ts
8
- /** `db.execute` returns either an array or `{ rows }`, depending on driver. */
9
- function rowsOf(result) {
10
- if (Array.isArray(result)) return result;
11
- const rows = result.rows;
12
- if (Array.isArray(rows)) return rows;
13
- throw new Error("previewProjection: db.execute() returned neither an array of rows nor { rows }.");
14
- }
15
- async function previewProjection(db, projection, opts) {
16
- const client = db;
17
- const { sql } = await import("drizzle-orm");
18
- return rowsOf(await client.execute(sql.raw(require_projection_sql.compileProjectionPreview(projection, opts.limit))));
19
- }
20
- //#endregion
21
7
  //#region ../sdk/src/drizzle-projection.ts
22
8
  /** `<ref>.<column>` — the token `fields` carries and cloud-ingest reads. */
23
9
  function refToken(ref) {
@@ -54,10 +40,12 @@ function fromDrizzleProjection(db, projection, opts) {
54
40
  const client = db;
55
41
  const known = /* @__PURE__ */ new Set([require_projection.BASE_REF, ...projection.joins.map((j) => j.key)]);
56
42
  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 }.`);
57
- const cursorRef = parseRef(opts.cursorField);
58
- assertKnownRef(cursorRef, known, "cursorField");
59
43
  for (const [field, binding] of Object.entries(projection.fieldBindings)) assertKnownRef(binding.from, known, `field "${field}"`);
60
44
  const identityExpr = require_projection_sql.compileProjectionIdentity(projection, opts.basePkColumn ?? "");
45
+ const fromClause = [require_projection_sql.compileProjectionFrom(projection), ...require_projection_sql.compileProjectionJoins(projection)].join(" ");
46
+ if (opts.cursorField === void 0) return fromDrizzleProjectionByIdentity(client, projection, identityExpr, fromClause, opts);
47
+ const cursorRef = parseRef(opts.cursorField);
48
+ assertKnownRef(cursorRef, known, "cursorField");
61
49
  const cursorToken = refToken(cursorRef);
62
50
  const cursorExpr = `CAST(${refSql(cursorRef)} AS timestamptz)`;
63
51
  const cursorRead = `to_char(${cursorExpr} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
@@ -69,11 +57,7 @@ function fromDrizzleProjection(db, projection, opts) {
69
57
  emitted.add(token);
70
58
  selectItems.push(`${refSql(binding.from)} AS ${require_projection_sql.quoteIdent(token)}`);
71
59
  }
72
- const head = [
73
- `select ${selectItems.join(", ")}`,
74
- require_projection_sql.compileProjectionFrom(projection),
75
- ...require_projection_sql.compileProjectionJoins(projection)
76
- ].join(" ");
60
+ const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
77
61
  return {
78
62
  entity: opts.entity,
79
63
  cursorField: cursorToken,
@@ -86,6 +70,49 @@ function fromDrizzleProjection(db, projection, opts) {
86
70
  }
87
71
  };
88
72
  }
73
+ /**
74
+ * The identity-only paging path `fromDrizzleProjection` takes when
75
+ * `cursorField` is omitted. Deliberately not a generalisation of the
76
+ * timestamp path above — it selects no timestamp cursor, pages on the
77
+ * identity expression alone, and always reports `cursorField`/`idField` as
78
+ * `"id"`, the alias every row carries either way.
79
+ */
80
+ function fromDrizzleProjectionByIdentity(client, projection, identityExpr, fromClause, opts) {
81
+ const selectItems = [`${identityExpr} AS ${require_projection_sql.quoteIdent("id")}`];
82
+ const emitted = /* @__PURE__ */ new Set();
83
+ for (const binding of Object.values(projection.fieldBindings)) {
84
+ const token = refToken(binding.from);
85
+ if (emitted.has(token)) continue;
86
+ emitted.add(token);
87
+ selectItems.push(`${refSql(binding.from)} AS ${require_projection_sql.quoteIdent(token)}`);
88
+ }
89
+ const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
90
+ return {
91
+ entity: opts.entity,
92
+ cursorField: "id",
93
+ idField: "id",
94
+ ...opts.objectType === void 0 ? {} : { objectType: opts.objectType },
95
+ fetchPage: async ({ since, limit }) => {
96
+ const where = since ? drizzle_orm.sql` where ${drizzle_orm.sql.raw(identityExpr)} > ${since.id}` : drizzle_orm.sql``;
97
+ const query = drizzle_orm.sql`${drizzle_orm.sql.raw(head)}${where} order by ${drizzle_orm.sql.raw(identityExpr)} asc limit ${limit}`;
98
+ return normalizeRows(await client.execute(query));
99
+ }
100
+ };
101
+ }
102
+ //#endregion
103
+ //#region ../sdk/src/projection-preview.ts
104
+ /** `db.execute` returns either an array or `{ rows }`, depending on driver. */
105
+ function rowsOf(result) {
106
+ if (Array.isArray(result)) return result;
107
+ const rows = result.rows;
108
+ if (Array.isArray(rows)) return rows;
109
+ throw new Error("previewProjection: db.execute() returned neither an array of rows nor { rows }.");
110
+ }
111
+ async function previewProjection(db, projection, opts) {
112
+ const client = db;
113
+ const { sql } = await import("drizzle-orm");
114
+ return rowsOf(await client.execute(sql.raw(require_projection_sql.compileProjectionPreview(projection, opts.limit))));
115
+ }
89
116
  //#endregion
90
117
  //#region ../sdk/src/drizzle.ts
91
118
  function inferIdField(table) {
@@ -152,6 +179,13 @@ function drizzleIntrospector(db) {
152
179
  },
153
180
  runProjection(projection, opts) {
154
181
  return previewProjection(db, projection, opts);
182
+ },
183
+ projectionSource(projection, opts) {
184
+ return fromDrizzleProjection(db, projection, {
185
+ entity: opts.objectType,
186
+ objectType: opts.objectType,
187
+ ...opts.basePkColumn ? { basePkColumn: opts.basePkColumn } : {}
188
+ });
155
189
  }
156
190
  };
157
191
  }
package/drizzle.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { F as ProjectionRef, P as ProjectionMapping } from "./entity-config-B2RA53nw.cjs";
2
- import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-DEtRrqtc.cjs";
3
- import { _ as SyncSource, t as NamespaceIntrospector } from "./namespace-probe-DaTJINS-.cjs";
2
+ import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-ixVV4pPN.cjs";
3
+ import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-DZIGdvlm.cjs";
4
4
  import { Table } from "drizzle-orm";
5
5
  //#region ../sdk/src/drizzle-projection.d.ts
6
6
  /**
@@ -37,8 +37,14 @@ interface FromDrizzleProjectionOptions {
37
37
  * The column to keyset-paginate on, as a ref token (`"base.updated_at"`) or
38
38
  * a `ProjectionRef`. A projection spans several tables, so there is no
39
39
  * single table to infer a cursor from.
40
+ *
41
+ * Omit it to page by identity alone — the mode a dashboard-authored
42
+ * projection uses, since it has no timestamp column and no UI to nominate
43
+ * one. `checkIdentitySafety` (`@clivly/core`) already proves the identity
44
+ * uniquely identifies a row, which makes it a sound keyset cursor with no
45
+ * timestamp involved.
40
46
  */
41
- cursorField: string | ProjectionRef;
47
+ cursorField?: string | ProjectionRef;
42
48
  /** The source name rows are pushed under. */
43
49
  entity: string;
44
50
  /**
package/drizzle.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { F as ProjectionRef, P as ProjectionMapping } from "./entity-config-B2RA53nw.mjs";
2
- import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-CiK8MMEZ.mjs";
3
- import { _ as SyncSource, t as NamespaceIntrospector } from "./namespace-probe-Ckn9yJ6F.mjs";
2
+ import { n as buildUniqueConstraints, r as discoverFromDrizzle } from "./drizzle-BmDDgAIF.mjs";
3
+ import { t as NamespaceIntrospector, v as SyncSource } from "./namespace-probe-BQ4imnV7.mjs";
4
4
  import { Table } from "drizzle-orm";
5
5
  //#region ../sdk/src/drizzle-projection.d.ts
6
6
  /**
@@ -37,8 +37,14 @@ interface FromDrizzleProjectionOptions {
37
37
  * The column to keyset-paginate on, as a ref token (`"base.updated_at"`) or
38
38
  * a `ProjectionRef`. A projection spans several tables, so there is no
39
39
  * single table to infer a cursor from.
40
+ *
41
+ * Omit it to page by identity alone — the mode a dashboard-authored
42
+ * projection uses, since it has no timestamp column and no UI to nominate
43
+ * one. `checkIdentitySafety` (`@clivly/core`) already proves the identity
44
+ * uniquely identifies a row, which makes it a sound keyset cursor with no
45
+ * timestamp involved.
40
46
  */
41
- cursorField: string | ProjectionRef;
47
+ cursorField?: string | ProjectionRef;
42
48
  /** The source name rows are pushed under. */
43
49
  entity: string;
44
50
  /**
package/drizzle.mjs CHANGED
@@ -1,22 +1,8 @@
1
1
  import { t as BASE_REF } from "./projection-hnPSgH6G.mjs";
2
2
  import { t as DRIZZLE_META } from "./drizzle-meta-D-5k3n0i.mjs";
3
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-3kvhkwO7.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
- //#region ../sdk/src/projection-preview.ts
7
- /** `db.execute` returns either an array or `{ rows }`, depending on driver. */
8
- function rowsOf(result) {
9
- if (Array.isArray(result)) return result;
10
- const rows = result.rows;
11
- if (Array.isArray(rows)) return rows;
12
- throw new Error("previewProjection: db.execute() returned neither an array of rows nor { rows }.");
13
- }
14
- async function previewProjection(db, projection, opts) {
15
- const client = db;
16
- const { sql } = await import("drizzle-orm");
17
- return rowsOf(await client.execute(sql.raw(compileProjectionPreview(projection, opts.limit))));
18
- }
19
- //#endregion
20
6
  //#region ../sdk/src/drizzle-projection.ts
21
7
  /** `<ref>.<column>` — the token `fields` carries and cloud-ingest reads. */
22
8
  function refToken(ref) {
@@ -53,10 +39,12 @@ function fromDrizzleProjection(db, projection, opts) {
53
39
  const client = db;
54
40
  const known = /* @__PURE__ */ new Set([BASE_REF, ...projection.joins.map((j) => j.key)]);
55
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 }.`);
56
- const cursorRef = parseRef(opts.cursorField);
57
- assertKnownRef(cursorRef, known, "cursorField");
58
42
  for (const [field, binding] of Object.entries(projection.fieldBindings)) assertKnownRef(binding.from, known, `field "${field}"`);
59
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");
60
48
  const cursorToken = refToken(cursorRef);
61
49
  const cursorExpr = `CAST(${refSql(cursorRef)} AS timestamptz)`;
62
50
  const cursorRead = `to_char(${cursorExpr} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
@@ -68,11 +56,7 @@ function fromDrizzleProjection(db, projection, opts) {
68
56
  emitted.add(token);
69
57
  selectItems.push(`${refSql(binding.from)} AS ${quoteIdent(token)}`);
70
58
  }
71
- const head = [
72
- `select ${selectItems.join(", ")}`,
73
- compileProjectionFrom(projection),
74
- ...compileProjectionJoins(projection)
75
- ].join(" ");
59
+ const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
76
60
  return {
77
61
  entity: opts.entity,
78
62
  cursorField: cursorToken,
@@ -85,6 +69,49 @@ function fromDrizzleProjection(db, projection, opts) {
85
69
  }
86
70
  };
87
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
+ }
88
115
  //#endregion
89
116
  //#region ../sdk/src/drizzle.ts
90
117
  function inferIdField(table) {
@@ -151,6 +178,13 @@ function drizzleIntrospector(db) {
151
178
  },
152
179
  runProjection(projection, opts) {
153
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
+ });
154
188
  }
155
189
  };
156
190
  }
@@ -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}" has no key/column metadata — upgrade @clivly/sdk and re-run discovery before using a projection`
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
- if (projection.identity.kind === "base_pk" && !projection.identity.column) {
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 (projection.identity.kind !== "composite") return;
205
- for (const part of projection.identity.parts) {
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;