clivly 0.9.6 → 0.10.0

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 (146) hide show
  1. package/README.md +24 -12
  2. package/{adapter-C2eRbBfo.d.cts → adapter-1TN0zohk.d.cts} +1 -1
  3. package/{adapter-D5LRa-NA.d.mts → adapter-uxirB88i.d.mts} +1 -1
  4. package/{auth-adapter-Bjgo8LhN.d.mts → auth-adapter-CXTiW3Mb.d.cts} +1 -1
  5. package/{auth-adapter-DWzgl5Yq.d.cts → auth-adapter-DSuYwv1c.d.mts} +1 -1
  6. package/auth-better-auth.d.cts +1 -1
  7. package/auth-better-auth.d.mts +1 -1
  8. package/auth-clerk.d.cts +1 -1
  9. package/auth-clerk.d.mts +1 -1
  10. package/auth-workos.d.cts +1 -1
  11. package/auth-workos.d.mts +1 -1
  12. package/core-adapter.d.cts +1 -1
  13. package/core-adapter.d.mts +1 -1
  14. package/core-auth-adapter.cjs +1 -1
  15. package/core-auth-adapter.d.cts +1 -1
  16. package/core-auth-adapter.d.mts +1 -1
  17. package/core-auth-adapter.mjs +1 -1
  18. package/core-drizzle.cjs +1 -1
  19. package/core-drizzle.d.cts +1 -1
  20. package/core-drizzle.d.mts +1 -1
  21. package/core-drizzle.mjs +1 -1
  22. package/core-entity-config.cjs +1 -1
  23. package/core-entity-config.d.cts +1 -1
  24. package/core-entity-config.d.mts +1 -1
  25. package/core-entity-config.mjs +1 -1
  26. package/core-entity-heuristics.cjs +1 -0
  27. package/core-entity-heuristics.d.cts +3 -3
  28. package/core-entity-heuristics.d.mts +3 -3
  29. package/core-entity-heuristics.mjs +2 -2
  30. package/core-mapping-form.cjs +1 -1
  31. package/core-mapping-form.d.cts +1 -1
  32. package/core-mapping-form.d.mts +1 -1
  33. package/core-mapping-form.mjs +1 -1
  34. package/core-mappings-config.cjs +1 -1
  35. package/core-mappings-config.d.cts +1 -1
  36. package/core-mappings-config.d.mts +1 -1
  37. package/core-mappings-config.mjs +1 -1
  38. package/core-sync-engine.cjs +1 -1
  39. package/core-sync-engine.d.cts +1 -1
  40. package/core-sync-engine.d.mts +1 -1
  41. package/core-sync-engine.mjs +1 -1
  42. package/core-types.cjs +4 -0
  43. package/core-types.d.cts +2 -2
  44. package/core-types.d.mts +2 -2
  45. package/core-types.mjs +2 -1
  46. package/core-view-compiler.cjs +1 -1
  47. package/core-view-compiler.d.cts +1 -1
  48. package/core-view-compiler.d.mts +1 -1
  49. package/core-view-compiler.mjs +1 -1
  50. package/core.cjs +18 -17
  51. package/core.d.cts +10 -10
  52. package/core.d.mts +10 -10
  53. package/core.mjs +10 -9
  54. package/{drizzle-DEtRrqtc.d.cts → drizzle-DTOuGXEn.d.cts} +4 -2
  55. package/{drizzle-bfjVzYR6.cjs → drizzle-DcB7OfBp.cjs} +9 -1
  56. package/{drizzle-CiK8MMEZ.d.mts → drizzle-SGapoly2.d.mts} +4 -2
  57. package/{drizzle-3kvhkwO7.mjs → drizzle-wbcuG-cy.mjs} +9 -1
  58. package/drizzle.cjs +58 -24
  59. package/drizzle.d.cts +10 -4
  60. package/drizzle.d.mts +10 -4
  61. package/drizzle.mjs +58 -24
  62. package/{entity-config-CusbTOJ1.mjs → entity-config-C4d3K9CP.mjs} +114 -8
  63. package/{entity-config-B2RA53nw.d.cts → entity-config-DNQAMbMS.d.cts} +27 -1
  64. package/{entity-config-B2RA53nw.d.mts → entity-config-DNQAMbMS.d.mts} +27 -1
  65. package/{entity-config-DE30_RpU.cjs → entity-config-DgAe0_YD.cjs} +119 -7
  66. package/index.cjs +56 -15
  67. package/index.mjs +56 -15
  68. package/internal-BAOG5eE4.d.mts +226 -0
  69. package/internal-BHJ0X3fC.d.cts +226 -0
  70. package/internal-Bgiimp5D.mjs +144 -0
  71. package/internal-Cz59xMj8.cjs +167 -0
  72. package/internal-adapter.d.cts +1 -1
  73. package/internal-adapter.d.mts +1 -1
  74. package/internal-auth-adapter.d.cts +1 -1
  75. package/internal-auth-adapter.d.mts +1 -1
  76. package/internal-drizzle.cjs +1 -1
  77. package/internal-drizzle.d.cts +1 -1
  78. package/internal-drizzle.d.mts +1 -1
  79. package/internal-drizzle.mjs +1 -1
  80. package/internal-entity-config.cjs +1 -1
  81. package/internal-entity-config.d.cts +1 -1
  82. package/internal-entity-config.d.mts +1 -1
  83. package/internal-entity-config.mjs +1 -1
  84. package/internal-entity-heuristics.cjs +34 -1
  85. package/internal-entity-heuristics.d.cts +8 -2
  86. package/internal-entity-heuristics.d.mts +8 -2
  87. package/internal-entity-heuristics.mjs +34 -2
  88. package/internal-mapping-form.d.cts +1 -1
  89. package/internal-mapping-form.d.mts +1 -1
  90. package/internal-mappings-config.cjs +1 -1
  91. package/internal-mappings-config.d.cts +1 -1
  92. package/internal-mappings-config.d.mts +1 -1
  93. package/internal-mappings-config.mjs +1 -1
  94. package/internal-sync-engine.cjs +1 -1
  95. package/internal-sync-engine.d.cts +1 -1
  96. package/internal-sync-engine.d.mts +1 -1
  97. package/internal-sync-engine.mjs +1 -1
  98. package/internal-types.cjs +4 -0
  99. package/internal-types.d.cts +2 -2
  100. package/internal-types.d.mts +2 -2
  101. package/internal-types.mjs +2 -1
  102. package/internal-view-compiler.cjs +1 -1
  103. package/internal-view-compiler.d.cts +1 -1
  104. package/internal-view-compiler.d.mts +1 -1
  105. package/internal-view-compiler.mjs +1 -1
  106. package/internal.cjs +17 -15
  107. package/internal.d.cts +10 -10
  108. package/internal.d.mts +10 -10
  109. package/internal.mjs +9 -8
  110. package/{mapping-form-DH77BXLv.d.mts → mapping-form-Cc7Ewv8c.d.mts} +1 -1
  111. package/{mapping-form-J0aU6O7l.d.cts → mapping-form-DwdbYAfR.d.cts} +1 -1
  112. package/{mappings-config-D9dsonoi.d.mts → mappings-config-Be9D43Np.d.mts} +1 -1
  113. package/{mappings-config-RQLGx8ub.d.cts → mappings-config-DUt4-9iO.d.cts} +1 -1
  114. package/{mappings-config-CjCmT-aU.cjs → mappings-config-Dx9ldgdf.cjs} +2 -16
  115. package/{mappings-config-7qQ47p9K.mjs → mappings-config-MoXBeI8p.mjs} +1 -15
  116. package/{namespace-probe-Ckn9yJ6F.d.mts → namespace-probe-9ScMDR0t.d.mts} +42 -2
  117. package/{namespace-probe-DaTJINS-.d.cts → namespace-probe-DC4tbmLj.d.cts} +42 -2
  118. package/package.json +1 -1
  119. package/{projection-hnPSgH6G.mjs → projection-CSGJbZlZ.mjs} +1 -1
  120. package/{projection-DEwL-D8R.cjs → projection-Dz6_i1-w.cjs} +0 -6
  121. package/{projection-sql-iHy-Cx6V.cjs → projection-sql-CbXCKt3s.cjs} +1 -1
  122. package/{projection-sql-9zzTVGvf.mjs → projection-sql-iHoiYr1R.mjs} +1 -1
  123. package/{sdk-w8Kv_XnW.cjs → sdk-B1vX4kUJ.cjs} +347 -17
  124. package/{sdk-Dkq6ELkf.mjs → sdk-D6gX0TXS.mjs} +347 -17
  125. package/sdk.cjs +2 -2
  126. package/sdk.d.cts +3 -3
  127. package/sdk.d.mts +3 -3
  128. package/sdk.mjs +2 -2
  129. package/{sync-engine-BLgwLpR6.cjs → sync-engine-B0FT1ax6.cjs} +1 -1
  130. package/{sync-engine-B_IzoUSx.mjs → sync-engine-CTSIcLlZ.mjs} +1 -1
  131. package/{sync-engine-BG2O1an2.d.cts → sync-engine-Df0kHAoJ.d.cts} +2 -2
  132. package/{sync-engine-DgQbrJON.d.mts → sync-engine-ugFacJ6G.d.mts} +2 -2
  133. package/{types-B1oRXdNP.d.cts → types-Cq1uHUKp.d.cts} +28 -2
  134. package/{types-B1oRXdNP.d.mts → types-Cq1uHUKp.d.mts} +28 -2
  135. package/types-Cq5gHCTq.cjs +20 -0
  136. package/types-YJTkXKzU.mjs +9 -0
  137. package/{view-compiler-D_6gfXFr.mjs → view-compiler-BzKk8nY3.mjs} +2 -2
  138. package/{view-compiler-CmEdAMwj.d.cts → view-compiler-D3zp9S3K.d.cts} +1 -1
  139. package/{view-compiler-Bo7pqXW3.d.mts → view-compiler-TpzDTu7z.d.mts} +1 -1
  140. package/{view-compiler-BHI5E4QI.cjs → view-compiler-ZKZbd3SK.cjs} +2 -2
  141. package/vite.d.cts +1 -1
  142. package/vite.d.mts +1 -1
  143. package/internal-BQ47iN6f.cjs +0 -259
  144. package/internal-ChufvXE6.mjs +0 -212
  145. package/internal-CvlFk3KC.d.mts +0 -180
  146. package/internal-DGUOLQAq.d.cts +0 -180
@@ -0,0 +1,20 @@
1
+ //#region ../core/src/types.ts
2
+ /** Named once so SQL predicates and UI fallbacks cannot drift apart. */
3
+ const OPEN_STAGE_TYPE = "open";
4
+ /** A closed deal is a historical fact: automation never reopens one. */
5
+ function isClosedStageType(type) {
6
+ return type === "won" || type === "lost";
7
+ }
8
+ //#endregion
9
+ Object.defineProperty(exports, "OPEN_STAGE_TYPE", {
10
+ enumerable: true,
11
+ get: function() {
12
+ return OPEN_STAGE_TYPE;
13
+ }
14
+ });
15
+ Object.defineProperty(exports, "isClosedStageType", {
16
+ enumerable: true,
17
+ get: function() {
18
+ return isClosedStageType;
19
+ }
20
+ });
@@ -0,0 +1,9 @@
1
+ //#region ../core/src/types.ts
2
+ /** Named once so SQL predicates and UI fallbacks cannot drift apart. */
3
+ const OPEN_STAGE_TYPE = "open";
4
+ /** A closed deal is a historical fact: automation never reopens one. */
5
+ function isClosedStageType(type) {
6
+ return type === "won" || type === "lost";
7
+ }
8
+ //#endregion
9
+ export { isClosedStageType as n, OPEN_STAGE_TYPE as t };
@@ -1,5 +1,5 @@
1
- import { t as BASE_REF } from "./projection-hnPSgH6G.mjs";
2
- import { i as compileProjectionJoins, n as compileProjectionFrom, o as compileProjectionSelect, s as quoteIdent } from "./projection-sql-9zzTVGvf.mjs";
1
+ import { t as BASE_REF } from "./projection-CSGJbZlZ.mjs";
2
+ import { i as compileProjectionJoins, n as compileProjectionFrom, o as compileProjectionSelect, s as quoteIdent } from "./projection-sql-iHoiYr1R.mjs";
3
3
  //#region ../core/src/view-compiler.ts
4
4
  /**
5
5
  * View compiler (Phase 2) — turns a validated entity config into read-only
@@ -1,4 +1,4 @@
1
- import { a as ClivlyEntitiesConfig } from "./entity-config-B2RA53nw.cjs";
1
+ import { a as ClivlyEntitiesConfig } from "./entity-config-DNQAMbMS.cjs";
2
2
  //#region ../core/src/view-compiler.d.ts
3
3
  declare const DEFAULT_VIEW_PREFIX = "crm_src_";
4
4
  interface CompileOptions {
@@ -1,4 +1,4 @@
1
- import { a as ClivlyEntitiesConfig } from "./entity-config-B2RA53nw.mjs";
1
+ import { a as ClivlyEntitiesConfig } from "./entity-config-DNQAMbMS.mjs";
2
2
  //#region ../core/src/view-compiler.d.ts
3
3
  declare const DEFAULT_VIEW_PREFIX = "crm_src_";
4
4
  interface CompileOptions {
@@ -1,5 +1,5 @@
1
- const require_projection = require("./projection-DEwL-D8R.cjs");
2
- const require_projection_sql = require("./projection-sql-iHy-Cx6V.cjs");
1
+ const require_projection = require("./projection-Dz6_i1-w.cjs");
2
+ const require_projection_sql = require("./projection-sql-CbXCKt3s.cjs");
3
3
  //#region ../core/src/view-compiler.ts
4
4
  /**
5
5
  * View compiler (Phase 2) — turns a validated entity config into read-only
package/vite.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { o as ClivlySDK } from "./namespace-probe-DaTJINS-.cjs";
1
+ import { o as ClivlySDK } from "./namespace-probe-DC4tbmLj.cjs";
2
2
  import { Plugin } from "vite";
3
3
  //#region ../sdk/src/vite.d.ts
4
4
  interface ClivlyViteOptions {
package/vite.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { o as ClivlySDK } from "./namespace-probe-Ckn9yJ6F.mjs";
1
+ import { o as ClivlySDK } from "./namespace-probe-9ScMDR0t.mjs";
2
2
  import { Plugin } from "vite";
3
3
  //#region ../sdk/src/vite.d.ts
4
4
  interface ClivlyViteOptions {
@@ -1,259 +0,0 @@
1
- const require_projection = require("./projection-DEwL-D8R.cjs");
2
- const require_entity_config = require("./entity-config-DE30_RpU.cjs");
3
- //#region ../core/src/projection-draft.ts
4
- function emptyProjectionDraft(baseTable) {
5
- return {
6
- baseTable,
7
- entityKey: "",
8
- fieldBindings: {},
9
- identity: { kind: "base_pk" },
10
- joins: []
11
- };
12
- }
13
- /**
14
- * The ON conditions implied by a foreign key between the two tables, so the
15
- * builder can pre-fill a join instead of making the user hand-pick columns.
16
- * Returns [] when no FK relates them — the user then picks manually.
17
- */
18
- function suggestJoinConditions(baseTable, joinTable, schema, joinKey) {
19
- const join = schema.find((t) => t.name === joinTable);
20
- const conditions = [];
21
- for (const column of join?.columnsMeta ?? []) if (column.references?.table === baseTable) conditions.push({
22
- left: {
23
- table: joinKey,
24
- column: column.name
25
- },
26
- op: "eq",
27
- right: {
28
- table: require_projection.BASE_REF,
29
- column: column.references.column
30
- }
31
- });
32
- return conditions;
33
- }
34
- /**
35
- * Add a join on `joinTable`, keyed and pre-filled.
36
- *
37
- * The key is the table's own name where that is free, because it is what the
38
- * user already calls the table; a collision falls back to `_2`, `_3` and so on.
39
- * Uniqueness is not cosmetic — the key is the SQL alias and the name every ref
40
- * addresses the table by, so two joins sharing one would make every ref
41
- * ambiguous. `base` is reserved for the base table and can never be taken.
42
- */
43
- function addDraftJoin(draft, joinTable, schema) {
44
- const taken = /* @__PURE__ */ new Set([require_projection.BASE_REF, ...draft.joins.map((j) => j.key)]);
45
- let key = joinTable;
46
- let suffix = 2;
47
- while (taken.has(key)) {
48
- key = `${joinTable}_${suffix}`;
49
- suffix += 1;
50
- }
51
- return {
52
- ...draft,
53
- joins: [...draft.joins, {
54
- key,
55
- table: joinTable,
56
- type: "left",
57
- on: suggestJoinConditions(draft.baseTable, joinTable, schema, key)
58
- }]
59
- };
60
- }
61
- /**
62
- * Remove a join, and everything that addressed it.
63
- *
64
- * Refs to a removed join are refs to a table the projection no longer declares.
65
- * Left in place they fail validation as "unknown ref table", which blames the
66
- * field — or, worse, the identity — for the join's removal. Identity falls back
67
- * to the base primary key, the same default a new draft starts from.
68
- */
69
- function removeDraftJoin(draft, joinKey) {
70
- const fieldBindings = Object.fromEntries(Object.entries(draft.fieldBindings).filter(([, binding]) => binding.from.table !== joinKey));
71
- const identityDependsOnJoin = require_projection.identityRefs(draft.identity, "").some((ref) => ref.table === joinKey);
72
- return {
73
- ...draft,
74
- joins: draft.joins.filter((join) => join.key !== joinKey),
75
- fieldBindings,
76
- identity: identityDependsOnJoin ? { kind: "base_pk" } : draft.identity
77
- };
78
- }
79
- /**
80
- * Every table a field or identity may currently read from, in ref order: the
81
- * base table first, then each join.
82
- *
83
- * Returns [] when the base table is not in the discovered schema — there is
84
- * nothing truthful to offer, and inventing columns would let someone build a
85
- * projection that cannot compile.
86
- */
87
- function draftRefOptions(draft, schema) {
88
- const byName = new Map(schema.map((table) => [table.name, table]));
89
- const base = byName.get(draft.baseTable);
90
- if (!base) return [];
91
- const options = [{
92
- ref: require_projection.BASE_REF,
93
- label: `${base.name} (${require_projection.BASE_REF})`,
94
- columns: base.columns
95
- }];
96
- for (const join of draft.joins) {
97
- const table = byName.get(join.table);
98
- if (table) options.push({
99
- ref: join.key,
100
- label: `${table.name} (${join.key})`,
101
- columns: table.columns
102
- });
103
- }
104
- return options;
105
- }
106
- /** A draft becomes a projection once it has a valid key and ≥1 binding. */
107
- function draftToProjection(draft) {
108
- if (!require_projection.PROJECTION_KEY_PATTERN.test(draft.entityKey)) return null;
109
- if (Object.keys(draft.fieldBindings).length === 0) return null;
110
- return {
111
- version: 1,
112
- kind: "projection",
113
- entityKey: draft.entityKey,
114
- baseTable: draft.baseTable,
115
- joins: draft.joins,
116
- fieldBindings: draft.fieldBindings,
117
- identity: draft.identity
118
- };
119
- }
120
- const PROBE_KEY = "draft";
121
- /**
122
- * A draft rendered as a projection so the semantic checks can run against it
123
- * while it is being written, not only once it is finished.
124
- *
125
- * `entityKey` is stood in for rather than taken from the draft: it is the one
126
- * field `validateProjection` never looks at, and a half-typed key would
127
- * otherwise have to be valid before any join or identity could be checked. The
128
- * missing or malformed key is reported separately, by `draftErrors`.
129
- */
130
- function probeProjection(draft) {
131
- return {
132
- version: 1,
133
- kind: "projection",
134
- entityKey: PROBE_KEY,
135
- baseTable: draft.baseTable,
136
- joins: draft.joins,
137
- fieldBindings: draft.fieldBindings,
138
- identity: draft.identity
139
- };
140
- }
141
- /**
142
- * `validateProjection` appends its own `projection.` segment to the base path it
143
- * is given, because in a config the base is the entity that owns the projection
144
- * (`entities.accounts` → `entities.accounts.projection.identity`). A draft has
145
- * no owning entity, so passing "projection" as the base produced
146
- * `projection.projection.identity` — a path no caller could match against, and
147
- * the reason `draftErrors` was only ever consumed as a flat list of messages.
148
- *
149
- * Passing an empty base and dropping the leading separator leaves one
150
- * convention: every path here starts `projection.` exactly once, whether it came
151
- * from this module or from the validator.
152
- */
153
- function semanticErrors(draft, schema) {
154
- return require_entity_config.validateProjection(probeProjection(draft), schema, "").map((error) => ({
155
- ...error,
156
- path: error.path.startsWith(".") ? error.path.slice(1) : error.path
157
- }));
158
- }
159
- /**
160
- * Every reason this draft cannot be saved, one per cause and each anchored to
161
- * the section that owns it.
162
- *
163
- * One lumped "Name the object and map a field." was all this used to return for
164
- * an incomplete draft, at the bare path "projection". Two consequences, both
165
- * bad: a builder had nowhere to attach the message, and — because it returned
166
- * before validating — a fanning join under a `base_pk` identity stayed silent
167
- * until the draft was otherwise complete, which is well after the point where
168
- * the user chose the join.
169
- */
170
- function draftErrors(draft, schema) {
171
- const errors = [];
172
- if (draft.entityKey === "") errors.push({
173
- path: "projection.entityKey",
174
- message: "Key is required."
175
- });
176
- else if (!require_projection.PROJECTION_KEY_PATTERN.test(draft.entityKey)) errors.push({
177
- path: "projection.entityKey",
178
- message: `Key ${require_projection.PROJECTION_KEY_MESSAGE}.`
179
- });
180
- if (Object.keys(draft.fieldBindings).length === 0) errors.push({
181
- path: "projection.fieldBindings",
182
- message: "Map at least one field."
183
- });
184
- if (draft.baseTable === "") {
185
- errors.push({
186
- path: "projection.baseTable",
187
- message: "Pick a base table."
188
- });
189
- return errors;
190
- }
191
- errors.push(...semanticErrors(draft, schema));
192
- return errors;
193
- }
194
- /**
195
- * Per-join fan-out, for the builder's inline cardinality hints.
196
- *
197
- * Answered from the joins alone. Joins are chosen before the key and the fields
198
- * are filled in, and a fan-out warning is precisely what should steer the
199
- * identity choice while it is being made — requiring a complete draft put the
200
- * hint after the decision it exists to inform.
201
- */
202
- function describeCardinality(draft, schema) {
203
- const projection = probeProjection(draft);
204
- const tablesByName = new Map(schema.map((t) => [t.name, t]));
205
- const fanning = new Set(require_entity_config.fanOutJoins(projection, tablesByName).map((j) => j.key));
206
- return projection.joins.map((join) => ({
207
- joinKey: join.key,
208
- fansOut: fanning.has(join.key)
209
- }));
210
- }
211
- //#endregion
212
- Object.defineProperty(exports, "addDraftJoin", {
213
- enumerable: true,
214
- get: function() {
215
- return addDraftJoin;
216
- }
217
- });
218
- Object.defineProperty(exports, "describeCardinality", {
219
- enumerable: true,
220
- get: function() {
221
- return describeCardinality;
222
- }
223
- });
224
- Object.defineProperty(exports, "draftErrors", {
225
- enumerable: true,
226
- get: function() {
227
- return draftErrors;
228
- }
229
- });
230
- Object.defineProperty(exports, "draftRefOptions", {
231
- enumerable: true,
232
- get: function() {
233
- return draftRefOptions;
234
- }
235
- });
236
- Object.defineProperty(exports, "draftToProjection", {
237
- enumerable: true,
238
- get: function() {
239
- return draftToProjection;
240
- }
241
- });
242
- Object.defineProperty(exports, "emptyProjectionDraft", {
243
- enumerable: true,
244
- get: function() {
245
- return emptyProjectionDraft;
246
- }
247
- });
248
- Object.defineProperty(exports, "removeDraftJoin", {
249
- enumerable: true,
250
- get: function() {
251
- return removeDraftJoin;
252
- }
253
- });
254
- Object.defineProperty(exports, "suggestJoinConditions", {
255
- enumerable: true,
256
- get: function() {
257
- return suggestJoinConditions;
258
- }
259
- });
@@ -1,212 +0,0 @@
1
- import { i as identityRefs, n as PROJECTION_KEY_MESSAGE, r as PROJECTION_KEY_PATTERN, t as BASE_REF } from "./projection-hnPSgH6G.mjs";
2
- import { h as fanOutJoins, m as validateProjection } from "./entity-config-CusbTOJ1.mjs";
3
- //#region ../core/src/projection-draft.ts
4
- function emptyProjectionDraft(baseTable) {
5
- return {
6
- baseTable,
7
- entityKey: "",
8
- fieldBindings: {},
9
- identity: { kind: "base_pk" },
10
- joins: []
11
- };
12
- }
13
- /**
14
- * The ON conditions implied by a foreign key between the two tables, so the
15
- * builder can pre-fill a join instead of making the user hand-pick columns.
16
- * Returns [] when no FK relates them — the user then picks manually.
17
- */
18
- function suggestJoinConditions(baseTable, joinTable, schema, joinKey) {
19
- const join = schema.find((t) => t.name === joinTable);
20
- const conditions = [];
21
- for (const column of join?.columnsMeta ?? []) if (column.references?.table === baseTable) conditions.push({
22
- left: {
23
- table: joinKey,
24
- column: column.name
25
- },
26
- op: "eq",
27
- right: {
28
- table: BASE_REF,
29
- column: column.references.column
30
- }
31
- });
32
- return conditions;
33
- }
34
- /**
35
- * Add a join on `joinTable`, keyed and pre-filled.
36
- *
37
- * The key is the table's own name where that is free, because it is what the
38
- * user already calls the table; a collision falls back to `_2`, `_3` and so on.
39
- * Uniqueness is not cosmetic — the key is the SQL alias and the name every ref
40
- * addresses the table by, so two joins sharing one would make every ref
41
- * ambiguous. `base` is reserved for the base table and can never be taken.
42
- */
43
- function addDraftJoin(draft, joinTable, schema) {
44
- const taken = /* @__PURE__ */ new Set([BASE_REF, ...draft.joins.map((j) => j.key)]);
45
- let key = joinTable;
46
- let suffix = 2;
47
- while (taken.has(key)) {
48
- key = `${joinTable}_${suffix}`;
49
- suffix += 1;
50
- }
51
- return {
52
- ...draft,
53
- joins: [...draft.joins, {
54
- key,
55
- table: joinTable,
56
- type: "left",
57
- on: suggestJoinConditions(draft.baseTable, joinTable, schema, key)
58
- }]
59
- };
60
- }
61
- /**
62
- * Remove a join, and everything that addressed it.
63
- *
64
- * Refs to a removed join are refs to a table the projection no longer declares.
65
- * Left in place they fail validation as "unknown ref table", which blames the
66
- * field — or, worse, the identity — for the join's removal. Identity falls back
67
- * to the base primary key, the same default a new draft starts from.
68
- */
69
- function removeDraftJoin(draft, joinKey) {
70
- const fieldBindings = Object.fromEntries(Object.entries(draft.fieldBindings).filter(([, binding]) => binding.from.table !== joinKey));
71
- const identityDependsOnJoin = identityRefs(draft.identity, "").some((ref) => ref.table === joinKey);
72
- return {
73
- ...draft,
74
- joins: draft.joins.filter((join) => join.key !== joinKey),
75
- fieldBindings,
76
- identity: identityDependsOnJoin ? { kind: "base_pk" } : draft.identity
77
- };
78
- }
79
- /**
80
- * Every table a field or identity may currently read from, in ref order: the
81
- * base table first, then each join.
82
- *
83
- * Returns [] when the base table is not in the discovered schema — there is
84
- * nothing truthful to offer, and inventing columns would let someone build a
85
- * projection that cannot compile.
86
- */
87
- function draftRefOptions(draft, schema) {
88
- const byName = new Map(schema.map((table) => [table.name, table]));
89
- const base = byName.get(draft.baseTable);
90
- if (!base) return [];
91
- const options = [{
92
- ref: BASE_REF,
93
- label: `${base.name} (${BASE_REF})`,
94
- columns: base.columns
95
- }];
96
- for (const join of draft.joins) {
97
- const table = byName.get(join.table);
98
- if (table) options.push({
99
- ref: join.key,
100
- label: `${table.name} (${join.key})`,
101
- columns: table.columns
102
- });
103
- }
104
- return options;
105
- }
106
- /** A draft becomes a projection once it has a valid key and ≥1 binding. */
107
- function draftToProjection(draft) {
108
- if (!PROJECTION_KEY_PATTERN.test(draft.entityKey)) return null;
109
- if (Object.keys(draft.fieldBindings).length === 0) return null;
110
- return {
111
- version: 1,
112
- kind: "projection",
113
- entityKey: draft.entityKey,
114
- baseTable: draft.baseTable,
115
- joins: draft.joins,
116
- fieldBindings: draft.fieldBindings,
117
- identity: draft.identity
118
- };
119
- }
120
- const PROBE_KEY = "draft";
121
- /**
122
- * A draft rendered as a projection so the semantic checks can run against it
123
- * while it is being written, not only once it is finished.
124
- *
125
- * `entityKey` is stood in for rather than taken from the draft: it is the one
126
- * field `validateProjection` never looks at, and a half-typed key would
127
- * otherwise have to be valid before any join or identity could be checked. The
128
- * missing or malformed key is reported separately, by `draftErrors`.
129
- */
130
- function probeProjection(draft) {
131
- return {
132
- version: 1,
133
- kind: "projection",
134
- entityKey: PROBE_KEY,
135
- baseTable: draft.baseTable,
136
- joins: draft.joins,
137
- fieldBindings: draft.fieldBindings,
138
- identity: draft.identity
139
- };
140
- }
141
- /**
142
- * `validateProjection` appends its own `projection.` segment to the base path it
143
- * is given, because in a config the base is the entity that owns the projection
144
- * (`entities.accounts` → `entities.accounts.projection.identity`). A draft has
145
- * no owning entity, so passing "projection" as the base produced
146
- * `projection.projection.identity` — a path no caller could match against, and
147
- * the reason `draftErrors` was only ever consumed as a flat list of messages.
148
- *
149
- * Passing an empty base and dropping the leading separator leaves one
150
- * convention: every path here starts `projection.` exactly once, whether it came
151
- * from this module or from the validator.
152
- */
153
- function semanticErrors(draft, schema) {
154
- return validateProjection(probeProjection(draft), schema, "").map((error) => ({
155
- ...error,
156
- path: error.path.startsWith(".") ? error.path.slice(1) : error.path
157
- }));
158
- }
159
- /**
160
- * Every reason this draft cannot be saved, one per cause and each anchored to
161
- * the section that owns it.
162
- *
163
- * One lumped "Name the object and map a field." was all this used to return for
164
- * an incomplete draft, at the bare path "projection". Two consequences, both
165
- * bad: a builder had nowhere to attach the message, and — because it returned
166
- * before validating — a fanning join under a `base_pk` identity stayed silent
167
- * until the draft was otherwise complete, which is well after the point where
168
- * the user chose the join.
169
- */
170
- function draftErrors(draft, schema) {
171
- const errors = [];
172
- if (draft.entityKey === "") errors.push({
173
- path: "projection.entityKey",
174
- message: "Key is required."
175
- });
176
- else if (!PROJECTION_KEY_PATTERN.test(draft.entityKey)) errors.push({
177
- path: "projection.entityKey",
178
- message: `Key ${PROJECTION_KEY_MESSAGE}.`
179
- });
180
- if (Object.keys(draft.fieldBindings).length === 0) errors.push({
181
- path: "projection.fieldBindings",
182
- message: "Map at least one field."
183
- });
184
- if (draft.baseTable === "") {
185
- errors.push({
186
- path: "projection.baseTable",
187
- message: "Pick a base table."
188
- });
189
- return errors;
190
- }
191
- errors.push(...semanticErrors(draft, schema));
192
- return errors;
193
- }
194
- /**
195
- * Per-join fan-out, for the builder's inline cardinality hints.
196
- *
197
- * Answered from the joins alone. Joins are chosen before the key and the fields
198
- * are filled in, and a fan-out warning is precisely what should steer the
199
- * identity choice while it is being made — requiring a complete draft put the
200
- * hint after the decision it exists to inform.
201
- */
202
- function describeCardinality(draft, schema) {
203
- const projection = probeProjection(draft);
204
- const tablesByName = new Map(schema.map((t) => [t.name, t]));
205
- const fanning = new Set(fanOutJoins(projection, tablesByName).map((j) => j.key));
206
- return projection.joins.map((join) => ({
207
- joinKey: join.key,
208
- fansOut: fanning.has(join.key)
209
- }));
210
- }
211
- //#endregion
212
- export { draftToProjection as a, suggestJoinConditions as c, draftRefOptions as i, describeCardinality as n, emptyProjectionDraft as o, draftErrors as r, removeDraftJoin as s, addDraftJoin as t };