@google-cloud/kcmd 0.0.0-stage → 0.2.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 (150) hide show
  1. package/LICENSE +203 -0
  2. package/README.md +264 -2
  3. package/build/ts/kcmd/cli.js +20252 -0
  4. package/build/ts/kcmd/gcp/alloydb.d.ts +12 -0
  5. package/build/ts/kcmd/gcp/alloydb.js +70 -0
  6. package/build/ts/kcmd/gcp/api.d.ts +29 -0
  7. package/build/ts/kcmd/gcp/api.js +162 -0
  8. package/build/ts/kcmd/gcp/biglake.d.ts +18 -0
  9. package/build/ts/kcmd/gcp/biglake.js +59 -0
  10. package/build/ts/kcmd/gcp/bigquery.d.ts +57 -0
  11. package/build/ts/kcmd/gcp/bigquery.js +134 -0
  12. package/build/ts/kcmd/gcp/context.d.ts +10 -0
  13. package/build/ts/kcmd/gcp/context.js +74 -0
  14. package/build/ts/kcmd/gcp/crm.d.ts +13 -0
  15. package/build/ts/kcmd/gcp/crm.js +91 -0
  16. package/build/ts/kcmd/gcp/dataplex.d.ts +147 -0
  17. package/build/ts/kcmd/gcp/dataplex.js +406 -0
  18. package/build/ts/kcmd/gcp/index.d.ts +3 -0
  19. package/build/ts/kcmd/gcp/index.js +21 -0
  20. package/build/ts/kcmd/gcp/spanner.d.ts +73 -0
  21. package/build/ts/kcmd/gcp/spanner.js +126 -0
  22. package/build/ts/kcmd/index.d.ts +7 -0
  23. package/build/ts/kcmd/index.js +46 -0
  24. package/build/ts/kcmd/layout.d.ts +16 -0
  25. package/build/ts/kcmd/layout.js +31 -0
  26. package/build/ts/kcmd/layouts/documents.d.ts +24 -0
  27. package/build/ts/kcmd/layouts/documents.js +264 -0
  28. package/build/ts/kcmd/layouts/okf.d.ts +31 -0
  29. package/build/ts/kcmd/layouts/okf.js +386 -0
  30. package/build/ts/kcmd/layouts/semantic-model.d.ts +74 -0
  31. package/build/ts/kcmd/layouts/semantic-model.js +621 -0
  32. package/build/ts/kcmd/layouts/standard.d.ts +13 -0
  33. package/build/ts/kcmd/layouts/standard.js +108 -0
  34. package/build/ts/kcmd/manifest.d.ts +34 -0
  35. package/build/ts/kcmd/manifest.js +262 -0
  36. package/build/ts/kcmd/metadata.d.ts +25 -0
  37. package/build/ts/kcmd/metadata.js +8 -0
  38. package/build/ts/kcmd/okf/catalog_residue.d.ts +14 -0
  39. package/build/ts/kcmd/okf/catalog_residue.js +124 -0
  40. package/build/ts/kcmd/okf/concept.d.ts +14 -0
  41. package/build/ts/kcmd/okf/concept.js +110 -0
  42. package/build/ts/kcmd/okf/concept_to_entry.d.ts +10 -0
  43. package/build/ts/kcmd/okf/concept_to_entry.js +281 -0
  44. package/build/ts/kcmd/okf/init_okf_workspace.d.ts +15 -0
  45. package/build/ts/kcmd/okf/init_okf_workspace.js +113 -0
  46. package/build/ts/kcmd/okf/links.d.ts +23 -0
  47. package/build/ts/kcmd/okf/links.js +468 -0
  48. package/build/ts/kcmd/okf/okf_metadata.d.ts +12 -0
  49. package/build/ts/kcmd/okf/okf_metadata.js +99 -0
  50. package/build/ts/kcmd/semantic/bigquery.d.ts +22 -0
  51. package/build/ts/kcmd/semantic/bigquery.js +469 -0
  52. package/build/ts/kcmd/semantic/binding.d.ts +3 -0
  53. package/build/ts/kcmd/semantic/binding.js +92 -0
  54. package/build/ts/kcmd/semantic/catalog_entry.d.ts +24 -0
  55. package/build/ts/kcmd/semantic/catalog_entry.js +35 -0
  56. package/build/ts/kcmd/semantic/converters/owl/convert.d.ts +26 -0
  57. package/build/ts/kcmd/semantic/converters/owl/convert.js +41 -0
  58. package/build/ts/kcmd/semantic/converters/owl/model.d.ts +98 -0
  59. package/build/ts/kcmd/semantic/converters/owl/model.js +30 -0
  60. package/build/ts/kcmd/semantic/converters/owl/parse.d.ts +11 -0
  61. package/build/ts/kcmd/semantic/converters/owl/parse.js +634 -0
  62. package/build/ts/kcmd/semantic/converters/owl/to_ir.d.ts +19 -0
  63. package/build/ts/kcmd/semantic/converters/owl/to_ir.js +390 -0
  64. package/build/ts/kcmd/semantic/deploy_bigquery.d.ts +20 -0
  65. package/build/ts/kcmd/semantic/deploy_bigquery.js +226 -0
  66. package/build/ts/kcmd/semantic/deploy_knowledge_catalog.d.ts +104 -0
  67. package/build/ts/kcmd/semantic/deploy_knowledge_catalog.js +1367 -0
  68. package/build/ts/kcmd/semantic/deploy_spanner.d.ts +15 -0
  69. package/build/ts/kcmd/semantic/deploy_spanner.js +150 -0
  70. package/build/ts/kcmd/semantic/deployment_target.d.ts +93 -0
  71. package/build/ts/kcmd/semantic/deployment_target.js +379 -0
  72. package/build/ts/kcmd/semantic/graph_ddl.d.ts +44 -0
  73. package/build/ts/kcmd/semantic/graph_ddl.js +656 -0
  74. package/build/ts/kcmd/semantic/import_model.d.ts +72 -0
  75. package/build/ts/kcmd/semantic/import_model.js +1698 -0
  76. package/build/ts/kcmd/semantic/ir.d.ts +589 -0
  77. package/build/ts/kcmd/semantic/ir.js +247 -0
  78. package/build/ts/kcmd/semantic/kc_actions.d.ts +45 -0
  79. package/build/ts/kcmd/semantic/kc_actions.js +531 -0
  80. package/build/ts/kcmd/semantic/kc_constraints.d.ts +44 -0
  81. package/build/ts/kcmd/semantic/kc_constraints.js +279 -0
  82. package/build/ts/kcmd/semantic/kc_converter.d.ts +37 -0
  83. package/build/ts/kcmd/semantic/kc_converter.js +839 -0
  84. package/build/ts/kcmd/semantic/kc_custom_types.d.ts +63 -0
  85. package/build/ts/kcmd/semantic/kc_custom_types.js +751 -0
  86. package/build/ts/kcmd/semantic/kc_entries.d.ts +155 -0
  87. package/build/ts/kcmd/semantic/kc_entries.js +284 -0
  88. package/build/ts/kcmd/semantic/kc_ids.d.ts +102 -0
  89. package/build/ts/kcmd/semantic/kc_ids.js +188 -0
  90. package/build/ts/kcmd/semantic/knowledge_catalog.d.ts +29 -0
  91. package/build/ts/kcmd/semantic/knowledge_catalog.js +960 -0
  92. package/build/ts/kcmd/semantic/loader.d.ts +258 -0
  93. package/build/ts/kcmd/semantic/loader.js +3576 -0
  94. package/build/ts/kcmd/semantic/names.d.ts +10 -0
  95. package/build/ts/kcmd/semantic/names.js +85 -0
  96. package/build/ts/kcmd/semantic/osi_converter.d.ts +60 -0
  97. package/build/ts/kcmd/semantic/osi_converter.js +910 -0
  98. package/build/ts/kcmd/semantic/parameters.d.ts +19 -0
  99. package/build/ts/kcmd/semantic/parameters.js +171 -0
  100. package/build/ts/kcmd/semantic/pull_kc.d.ts +10 -0
  101. package/build/ts/kcmd/semantic/pull_kc.js +272 -0
  102. package/build/ts/kcmd/semantic/resolve_inheritance.d.ts +59 -0
  103. package/build/ts/kcmd/semantic/resolve_inheritance.js +346 -0
  104. package/build/ts/kcmd/semantic/resolve_profiles.d.ts +291 -0
  105. package/build/ts/kcmd/semantic/resolve_profiles.js +2003 -0
  106. package/build/ts/kcmd/semantic/runtime/agent_tools.d.ts +93 -0
  107. package/build/ts/kcmd/semantic/runtime/agent_tools.js +243 -0
  108. package/build/ts/kcmd/semantic/runtime/dialect.d.ts +13 -0
  109. package/build/ts/kcmd/semantic/runtime/dialect.js +39 -0
  110. package/build/ts/kcmd/semantic/runtime/runtime.d.ts +88 -0
  111. package/build/ts/kcmd/semantic/runtime/runtime.js +301 -0
  112. package/build/ts/kcmd/semantic/runtime/store.d.ts +112 -0
  113. package/build/ts/kcmd/semantic/runtime/store.js +344 -0
  114. package/build/ts/kcmd/semantic/skills.d.ts +110 -0
  115. package/build/ts/kcmd/semantic/skills.js +697 -0
  116. package/build/ts/kcmd/semantic/source_dialect.d.ts +17 -0
  117. package/build/ts/kcmd/semantic/source_dialect.js +311 -0
  118. package/build/ts/kcmd/semantic/source_entries.d.ts +12 -0
  119. package/build/ts/kcmd/semantic/source_entries.js +341 -0
  120. package/build/ts/kcmd/semantic/spanner.d.ts +18 -0
  121. package/build/ts/kcmd/semantic/spanner.js +60 -0
  122. package/build/ts/kcmd/semantic/sql_expr_utils.d.ts +23 -0
  123. package/build/ts/kcmd/semantic/sql_expr_utils.js +394 -0
  124. package/build/ts/kcmd/semantic/sql_identifiers.d.ts +6 -0
  125. package/build/ts/kcmd/semantic/sql_identifiers.js +267 -0
  126. package/build/ts/kcmd/semantic/sql_parser.d.ts +47 -0
  127. package/build/ts/kcmd/semantic/sql_parser.js +400 -0
  128. package/build/ts/kcmd/semantic/transpile.d.ts +76 -0
  129. package/build/ts/kcmd/semantic/transpile.js +581 -0
  130. package/build/ts/kcmd/semantic/validate.d.ts +70 -0
  131. package/build/ts/kcmd/semantic/validate.js +2266 -0
  132. package/build/ts/kcmd/semantic/yaml_options.d.ts +6 -0
  133. package/build/ts/kcmd/semantic/yaml_options.js +58 -0
  134. package/build/ts/kcmd/semantic/yaml_update.d.ts +26 -0
  135. package/build/ts/kcmd/semantic/yaml_update.js +782 -0
  136. package/build/ts/kcmd/snapshot.d.ts +37 -0
  137. package/build/ts/kcmd/snapshot.js +386 -0
  138. package/build/ts/kcmd/source.d.ts +31 -0
  139. package/build/ts/kcmd/source.js +119 -0
  140. package/build/ts/kcmd/sources/bq-dataset.d.ts +17 -0
  141. package/build/ts/kcmd/sources/bq-dataset.js +131 -0
  142. package/build/ts/kcmd/sources/entrygroup.d.ts +21 -0
  143. package/build/ts/kcmd/sources/entrygroup.js +83 -0
  144. package/build/ts/kcmd/sources/kb.d.ts +21 -0
  145. package/build/ts/kcmd/sources/kb.js +82 -0
  146. package/build/ts/kcmd/sources/semantic-model.d.ts +18 -0
  147. package/build/ts/kcmd/sources/semantic-model.js +48 -0
  148. package/build/ts/kcmd/sync.d.ts +35 -0
  149. package/build/ts/kcmd/sync.js +734 -0
  150. package/package.json +62 -4
@@ -0,0 +1,346 @@
1
+ "use strict";
2
+ // Resolves entity-level inheritance (`extends`) into self-contained entities.
3
+ //
4
+ // The IR records inheritance AS DECLARED: an entity's `extends` names its
5
+ // direct supertypes, and its `fields` are only the ones it declares itself (see
6
+ // ir.Entity.extends). That is faithful but not directly consumable -- an
7
+ // emitter that wants to publish a subclass as a first-class node needs the
8
+ // supertype's fields present on the child, and the full ancestor set (not just
9
+ // the direct parents) to label it. This pass performs that resolution,
10
+ // mirroring the shape of ./transpile: it `structuredClone`s the model and NEVER
11
+ // mutates its input, returning a resolved clone plus warnings.
12
+ //
13
+ // What it does, per entity:
14
+ // - Fields FLOW DOWN (flattening). The entity's own fields come first
15
+ // (declared order), then every field it inherits. An inherited field is
16
+ // MERGED down the chain that declares it: the farthest declaration supplies
17
+ // the definition (type, label, description, ai_context, ...), and each
18
+ // nearer redeclaration, the entity's own included, supplies only its
19
+ // binding. A subtype that rebinds a field to another column therefore keeps
20
+ // everything its parent said about it.
21
+ // - `extends` is expanded from the direct parents to the full, de-duplicated
22
+ // TRANSITIVE ancestor set, ordered nearest-first (a diamond lists each
23
+ // ancestor once). An emitter reads labels straight off this list.
24
+ //
25
+ // What it deliberately does NOT do:
26
+ // - Keys are NOT inherited: each entity keeps its own KEY (a node table is
27
+ // identified by its own grain, not its supertype's).
28
+ // - Relationships (edges) are NOT inherited: a subclass does not gain its
29
+ // supertype's edges. Only node properties flow down.
30
+ // - It does not classify abstract vs concrete or drop anything -- that is the
31
+ // consuming leg's concern (see graph_ddl.ts). This pass is leg-agnostic.
32
+ //
33
+ // Errors, all thrown: a cycle in `extends`; a parent that is not an entity in
34
+ // the model; a field inherited from two ancestors that each declare it with
35
+ // neither extending the other, since nothing says which definition wins; and
36
+ // two fields of one entity, declared or inherited, whose names differ only in
37
+ // case. A diamond is not the third case: two paths reaching ONE declaration
38
+ // resolve to it, and an exact redeclaration of an inherited field rebinds it.
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.InheritanceError = void 0;
41
+ exports.declaredConceptFields = declaredConceptFields;
42
+ exports.resolveInheritance = resolveInheritance;
43
+ const ir_1 = require("./ir");
44
+ const sql_expr_utils_1 = require("./sql_expr_utils");
45
+ /**
46
+ * Why inheritance cannot be resolved. `unknown-parent` can be a pruning
47
+ * artefact, since a profile can drop a supertype whole; a cycle or an
48
+ * ambiguously inherited field cannot, because pruning only removes things.
49
+ */
50
+ class InheritanceError extends Error {
51
+ kind;
52
+ constructor(kind, message) {
53
+ super(message);
54
+ this.kind = kind;
55
+ }
56
+ }
57
+ exports.InheritanceError = InheritanceError;
58
+ function declaredConceptFields(model) {
59
+ const inherits = (model.entities ?? []).some(e => e.extends?.length);
60
+ const entities = (inherits ? resolveInheritance(model).model : model).entities ?? [];
61
+ const concepts = new Map();
62
+ for (const e of entities) {
63
+ concepts.set(e.name, {
64
+ kind: 'entity',
65
+ fields: new Map((e.fields ?? []).map(f => [f.name, f])),
66
+ });
67
+ }
68
+ for (const r of model.relationships ?? []) {
69
+ if (concepts.has(r.name))
70
+ continue;
71
+ concepts.set(r.name, {
72
+ kind: 'relationship',
73
+ fields: new Map((r.association?.fields ?? []).map(f => [f.name, f])),
74
+ });
75
+ }
76
+ return concepts;
77
+ }
78
+ /**
79
+ * Returns a clone of `model` with every entity's `extends` expanded to its full
80
+ * transitive ancestor set and its `fields` flattened to include inherited
81
+ * fields, less any a profile excludes on that entity. The input is never
82
+ * mutated. An entity with no `extends` keeps its own fields, so a model with no
83
+ * inheritance and no exclusions resolves to an equivalent model.
84
+ * `targetDialect` is the SQL that an inherited field's target text and its
85
+ * ANSI_SQL entries are read in to find their comments. It is BigQuery unless
86
+ * given, as the graph generators read the target text. With
87
+ * `options.requalify`, an inherited expression keeps naming the fields it
88
+ * names on its ancestor, under the inheriting entity's qualifier.
89
+ *
90
+ * Reading an inherited field's SQL needs the SQL parser, so call
91
+ * `loadSqlEngine()` first. Without it, this throws the parser's plain Error.
92
+ */
93
+ function resolveInheritance(model, targetDialect, options = {}) {
94
+ const clone = structuredClone(model);
95
+ const warnings = [];
96
+ const entities = clone.entities ?? [];
97
+ const byName = new Map(entities.map(e => [e.name, e]));
98
+ // Snapshot the AS-DECLARED direct parents and own fields BEFORE mutating any
99
+ // entity, so resolution reads a stable view regardless of iteration order
100
+ // (each entity is flattened from originals, not from an already-flattened
101
+ // ancestor).
102
+ const directParents = new Map();
103
+ const ownFields = new Map();
104
+ for (const e of entities) {
105
+ directParents.set(e.name, [...(e.extends ?? [])]);
106
+ ownFields.set(e.name, [...e.fields]);
107
+ }
108
+ // Every entity's ancestors, nearest-first, computed once: the merge below
109
+ // needs to know whether one declaring ancestor descends from another.
110
+ const ancestorsOf = new Map();
111
+ for (const e of entities) {
112
+ ancestorsOf.set(e.name, transitiveAncestors(e.name, byName, directParents));
113
+ }
114
+ for (const entity of entities) {
115
+ const ancestors = ancestorsOf.get(entity.name) ?? [];
116
+ // Which ancestors declare each inherited field name, nearest-first. An
117
+ // ancestor that redeclares a field it inherits and sets nothing on it
118
+ // changes nothing, so it is not a declarer. Counting it would make a
119
+ // diamond ambiguous over a line that has no effect.
120
+ const declarers = new Map();
121
+ const inheritedByLower = new Map();
122
+ for (const anc of ancestors) {
123
+ for (const f of ownFields.get(anc) ?? []) {
124
+ const lower = f.name.toLowerCase();
125
+ const prior = inheritedByLower.get(lower);
126
+ if (prior && prior.name !== f.name) {
127
+ throw new InheritanceError('ambiguous-field', `entity '${entity.name}' inherits field '${prior.name}' from '${prior.from}' and field '${f.name}' from '${anc}' (case-insensitive collision); field names on an ` +
128
+ `entity must be unique ignoring case`);
129
+ }
130
+ if (setsNothing(f) &&
131
+ (ancestorsOf.get(anc) ?? [])
132
+ .some(a => ownFields.get(a)?.some(g => g.name === f.name))) {
133
+ continue;
134
+ }
135
+ if (!prior)
136
+ inheritedByLower.set(lower, { name: f.name, from: anc });
137
+ const list = declarers.get(f.name) ?? [];
138
+ if (!list.includes(anc))
139
+ list.push(anc);
140
+ declarers.set(f.name, list);
141
+ }
142
+ }
143
+ for (const [name, list] of declarers) {
144
+ requireOneChain(entity.name, name, list, ancestorsOf);
145
+ }
146
+ // Own fields first, then every inherited field, each merged down its
147
+ // declaring chain. A subtype's redeclaration of an inherited field merges
148
+ // over that chain too, contributing only its binding. With `requalify`,
149
+ // each inherited expression names this entity's fields.
150
+ const into = options.requalify ? entity.name : undefined;
151
+ const seenField = new Set();
152
+ const flattened = [];
153
+ for (const f of ownFields.get(entity.name) ?? []) {
154
+ const inherited = inheritedByLower.get(f.name.toLowerCase());
155
+ if (inherited && inherited.name !== f.name) {
156
+ throw new InheritanceError('ambiguous-field', `entity '${entity.name}' declares field '${f.name}', which collides ignoring case with field '${inherited.name}' inherited from '${inherited.from}'; field names on an entity must be unique ` +
157
+ `ignoring case`);
158
+ }
159
+ if (seenField.has(f.name))
160
+ continue; // a self-duplicate; keep the first
161
+ seenField.add(f.name);
162
+ const chain = declarers.get(f.name);
163
+ flattened.push(chain ? mergeBinding(mergeChain(f.name, chain, ownFields, ancestorsOf, targetDialect, into), f) :
164
+ f);
165
+ }
166
+ for (const [name, chain] of declarers) {
167
+ if (seenField.has(name))
168
+ continue;
169
+ seenField.add(name);
170
+ flattened.push(mergeChain(name, chain, ownFields, ancestorsOf, targetDialect, into));
171
+ }
172
+ // A field the binding profile excludes on this entity is left off it.
173
+ // Descendants read the snapshot of declared fields, so they still inherit
174
+ // it.
175
+ const excluded = new Set(entity.excludedFields ?? []);
176
+ entity.fields = excluded.size ?
177
+ flattened.filter(f => !excluded.has(f.name)) :
178
+ flattened;
179
+ // Expand `extends` to the resolved ancestor list. Drop the key when the
180
+ // entity has no parents, so a consumer reads `extends` as the exact label
181
+ // set.
182
+ if (ancestors.length) {
183
+ entity.extends = ancestors;
184
+ }
185
+ else {
186
+ delete entity.extends;
187
+ }
188
+ }
189
+ return { model: clone, warnings: [...new Set(warnings)] };
190
+ }
191
+ // Clones an ancestor's field for a descendant, rewriting its expression to be
192
+ // TABLE-LOCAL. A field expression written on the ancestor as `<Ancestor>.col`
193
+ // names the ancestor's field `col`; inherited onto a
194
+ // descendant -- whose backing table carries the same column, since inherited
195
+ // fields must physically exist on the child -- it must reference the
196
+ // DESCENDANT's column, so the ancestor's own-name qualifier is stripped.
197
+ // Without this the descendant would render `<Ancestor>.col AS col` while the
198
+ // ancestor renders `col`, and an emitter that reuses one label across both
199
+ // tables (e.g. BigQuery's shared labels) would see two different definitions of
200
+ // the same property and reject the graph.
201
+ //
202
+ // Given `into`, the descendant, the ancestor's qualifier is renamed to `into`'s
203
+ // instead. `<Ancestor>.x` names the ancestor's field `x`, and the graph
204
+ // renders such a name from that field's own SQL, so inherited it must name the
205
+ // descendant's `x`, which may bind another column; stripped, it would read a
206
+ // column `x` the descendant's table may not have. The graph
207
+ // generators, and the checks that guard them, ask for this.
208
+ //
209
+ // This composes with (does not duplicate) the emitter's own qualifier
210
+ // stripping: this pass normalizes an inherited expression INTO the child's
211
+ // frame once here, and the emitter's table-local renderer then strips the
212
+ // child's own qualifier uniformly for every field, reading each sibling it
213
+ // names (see graph_ddl.renderFieldPropertyCore). Both find the qualifier with
214
+ // the SQL parser (see sql_expr_utils.qualifiersIn).
215
+ //
216
+ // The text comes out without its comments, and the rest stays as written. The
217
+ // target text is read in `targetDialect`, BigQuery unless given, and so are
218
+ // the ANSI_SQL entries, which a database of that dialect runs as its own SQL.
219
+ // The imported text is read in its imported dialect, and every other dialect
220
+ // entry in its own, so a quoted qualifier (`"Person".name` in PostgreSQL) is
221
+ // found as that dialect quotes it; renamed, it keeps its quotes. A vendor
222
+ // entry that holds the same text as the target does not change how the target
223
+ // is read.
224
+ function localizeInheritedField(field, ancestor, targetDialect, into) {
225
+ const clone = structuredClone(field);
226
+ const local = (text, dialect) => {
227
+ const bare = (0, sql_expr_utils_1.sqlWithoutComments)(text, dialect);
228
+ return into === undefined ? (0, sql_expr_utils_1.withoutQualifier)(bare, ancestor, dialect) :
229
+ (0, sql_expr_utils_1.requalified)(bare, ancestor, into, dialect);
230
+ };
231
+ if (clone.expression !== undefined) {
232
+ clone.expression = local(clone.expression, targetDialect);
233
+ }
234
+ if (clone.importedExpression !== undefined) {
235
+ clone.importedExpression =
236
+ local(clone.importedExpression, clone.importedDialect);
237
+ }
238
+ if (clone.dialects) {
239
+ const read = (d) => d === 'ANSI_SQL' ? targetDialect : d;
240
+ clone.dialects = clone.dialects.map(d => ({ ...d, expression: local(d.expression, read(d.dialect)) }));
241
+ }
242
+ return clone;
243
+ }
244
+ // `over` laid onto `base`: every property `over` sets wins, and every one it
245
+ // leaves unset comes from `base`. When `over` carries SQL, `base`'s binding is
246
+ // dropped whole, so an expression and a dialect list from two different
247
+ // declarations never end up on one field. When it carries none, its binding
248
+ // properties (a lone `stringForm` or `importedDialect`) are ignored and
249
+ // `base`'s binding stands.
250
+ function mergeBinding(base, over) {
251
+ const merged = structuredClone(base);
252
+ const bound = (0, ir_1.isFieldBound)(over);
253
+ if (bound) {
254
+ for (const k of ir_1.FIELD_BINDING_KEYS)
255
+ delete merged[k];
256
+ }
257
+ const binding = new Set(ir_1.FIELD_BINDING_KEYS);
258
+ for (const [k, v] of Object.entries(over)) {
259
+ if (v === undefined || (!bound && binding.has(k)))
260
+ continue;
261
+ merged[k] = structuredClone(v);
262
+ }
263
+ return merged;
264
+ }
265
+ // The field `name` as inherited down `chain`, the ancestors that declare it.
266
+ // The chain is folded in order of descent, root first: the root's declaration
267
+ // is the definition, and each descendant's lays its binding over it.
268
+ // Breadth-first
269
+ // distance is not that order when two paths to the entity differ in length, so
270
+ // the chain is sorted by how many ancestors each member has; requireOneChain
271
+ // has already checked that every member descends from the next. Every
272
+ // expression is localized to the declaring ancestor's frame first, its target
273
+ // text read in `targetDialect`, and moved onto `into` when given.
274
+ function mergeChain(name, chain, ownFields, ancestorsOf, targetDialect, into) {
275
+ const depth = (n) => (ancestorsOf.get(n) ?? []).length;
276
+ let merged;
277
+ for (const anc of [...chain].sort((a, b) => depth(a) - depth(b))) {
278
+ const own = (ownFields.get(anc) ?? []).find(f => f.name === name);
279
+ const local = localizeInheritedField(own, anc, targetDialect, into);
280
+ merged = merged ? mergeBinding(merged, local) : local;
281
+ }
282
+ return merged;
283
+ }
284
+ // Whether a field declaration carries neither a binding nor any part of a
285
+ // definition. Redeclaring an inherited field this way is a no-op.
286
+ function setsNothing(field) {
287
+ return !(0, ir_1.isFieldBound)(field) &&
288
+ ir_1.FIELD_DEFINITION_KEYS.every(k => field[k] === undefined);
289
+ }
290
+ // Throws unless every ancestor in `declaring` lies on one line of descent, so
291
+ // that one declaration plainly overrides another. Two ancestors that each
292
+ // declare the field, with neither extending the other, leave its definition
293
+ // ambiguous.
294
+ function requireOneChain(entity, field, declaring, ancestorsOf) {
295
+ for (let i = 0; i < declaring.length; i++) {
296
+ for (let j = i + 1; j < declaring.length; j++) {
297
+ const a = declaring[i], b = declaring[j];
298
+ const related = (ancestorsOf.get(a) ?? []).includes(b) ||
299
+ (ancestorsOf.get(b) ?? []).includes(a);
300
+ if (!related) {
301
+ throw new InheritanceError('ambiguous-field', `entity '${entity}' inherits field '${field}' from '${a}' and ` +
302
+ `from '${b}', and neither extends the other, so nothing says ` +
303
+ `which wins; declare or rebind '${field}' on only one of them`);
304
+ }
305
+ }
306
+ }
307
+ }
308
+ // Computes the de-duplicated transitive ancestor set for `start`, ORDERED
309
+ // NEAREST-FIRST (breadth-first by distance): direct parents in declared order,
310
+ // then grandparents, and so on. Nearest-first is what makes field flattening's
311
+ // "nearest definition wins" correct -- a field defined by both a direct parent
312
+ // and a grandparent must resolve to the direct parent's. Direct parents are
313
+ // read from the pre-mutation snapshot so resolution is order-independent.
314
+ //
315
+ // A parent edge that points back to `start` is a cycle, and a parent that is
316
+ // not an entity in the model is a typo; both are hard errors. A cycle that does
317
+ // not pass through `start` is caught when an entity on it is resolved, and
318
+ // every entity is. A node re-reached through a second path (a diamond) is
319
+ // simply skipped -- it is already included at its nearest distance.
320
+ function transitiveAncestors(start, byName, directParents) {
321
+ const result = [];
322
+ const seen = new Set([start]);
323
+ // Queue of (child that declared the edge, parent) so a cycle error can name
324
+ // the offending child. Seeded with `start`'s direct parents in declared
325
+ // order.
326
+ const queue = (directParents.get(start) ?? []).map(name => ({ from: start, name }));
327
+ while (queue.length) {
328
+ const { from, name } = queue.shift();
329
+ if (name === start) {
330
+ throw new InheritanceError('cycle', `entity '${from}' extends '${name}', which is already a supertype ` +
331
+ `on this chain; 'extends' must not form a cycle`);
332
+ }
333
+ if (!byName.has(name)) {
334
+ throw new InheritanceError('unknown-parent', `entity '${from}' extends unknown entity '${name}'; it is not ` +
335
+ `defined in the model`);
336
+ }
337
+ if (seen.has(name))
338
+ continue; // diamond: already included at its nearest
339
+ seen.add(name);
340
+ result.push(name);
341
+ for (const gp of directParents.get(name) ?? []) {
342
+ queue.push({ from: name, name: gp });
343
+ }
344
+ }
345
+ return result;
346
+ }
@@ -0,0 +1,291 @@
1
+ import { Entity, Field, ProfileSpec, SemanticModel } from './ir';
2
+ import { LoadOptions, SemanticModelError } from './loader';
3
+ export declare const DEFAULT_PROFILE = "default";
4
+ export declare function plainCopy(value: unknown): any;
5
+ export interface AvailabilityReport {
6
+ profile: string;
7
+ unboundFields: string[];
8
+ excludedFields: string[];
9
+ droppedEntities: {
10
+ name: string;
11
+ reason: string;
12
+ }[];
13
+ droppedMetrics: {
14
+ name: string;
15
+ reason: string;
16
+ field?: string;
17
+ }[];
18
+ droppedRelationships: {
19
+ name: string;
20
+ reason: string;
21
+ }[];
22
+ droppedActions: {
23
+ name: string;
24
+ reason: string;
25
+ }[];
26
+ droppedFields: {
27
+ name: string;
28
+ reason: string;
29
+ }[];
30
+ warnings: string[];
31
+ }
32
+ /**
33
+ * Returns a clone of `model` reduced to what `profileName` can answer: unbound
34
+ * and excluded fields taken off their entities, with every field that reads
35
+ * one of them, directly or through other fields; every excluded metric and
36
+ * every metric that reads an unavailable field dropped; and every action with
37
+ * no executor dropped. A field counts whether the entity declares or inherits
38
+ * it. What a field or metric reads comes from the text `dialect` evaluates
39
+ * (see textsFor). `dialect` defaults to the dialect the model's sources
40
+ * imply. When the sources are in systems of different dialects, or one names
41
+ * no system, there is no dialect and every text counts. When no lookup read
42
+ * the dialect, it is null and no text counts.
43
+ * The input is never mutated. The report names each dropped block
44
+ * and the unbound field that stops it, so a caller can state the withheld
45
+ * coverage. A model with nothing unbound resolves unchanged.
46
+ */
47
+ export declare function pruneUnavailable(model: SemanticModel, profileName: string, dialect?: string | null | undefined): {
48
+ model: SemanticModel;
49
+ report: AvailabilityReport;
50
+ };
51
+ export declare function withoutExclusions(model: SemanticModel): SemanticModel;
52
+ export declare function resolvedEntities(model: SemanticModel): Entity[];
53
+ export declare function inheritedNamesOf(model: SemanticModel, entityName: string, byName?: Map<string, Entity>): Set<string>;
54
+ /**
55
+ * Reads a profile file into the IR's `ProfileSpec`. Throws on anything a
56
+ * profile may not say: an unknown key at any level, a value of the wrong shape,
57
+ * a field in both `fields` and `fields_exclude`, `"*"` inside a
58
+ * `metrics_exclude` list, or a `name` other than `profileName`. A field
59
+ * expression and an action's executor are read by the loader's own readers, so
60
+ * each follows the rules it follows in a model file. A source is kept as
61
+ * written, and applyProfile reads it with the loader's reader too. A field
62
+ * expression's short form means `ANSI_SQL` in either flavor. Actions are out of
63
+ * scope for the preview, so nothing more is checked. With `skipActions`, the
64
+ * profile's actions are not read.
65
+ */
66
+ export declare function loadProfileFile(text: string, profileName: string, opts?: {
67
+ skipActions?: boolean;
68
+ }): ProfileSpec;
69
+ export type DialectLoad = Pick<LoadOptions, 'sourceDialects'> & {
70
+ sourceDatabases?: ReadonlyMap<string, string>;
71
+ };
72
+ /** How a profile's values are read, matching the load of the model. */
73
+ export interface ApplyProfileOptions {
74
+ load?: Pick<LoadOptions, 'dialect' | 'sourceDialects'>;
75
+ warnings?: string[];
76
+ from?: {
77
+ document: string;
78
+ warnings: string[];
79
+ };
80
+ }
81
+ /**
82
+ * Returns a copy of `model` with `profile` applied: each named entity's
83
+ * source, its keys where the profile states them, its field bindings, as a
84
+ * binding-only redeclaration for a field the entity inherits, and its
85
+ * `fields_exclude`; each relationship's join columns; `metrics_exclude`; and
86
+ * each action's executor, which is taken as given, since actions are out of
87
+ * preview scope. A field a profile binds keeps every dialect text the profile
88
+ * wrote. An excluded field is marked in `excludedFields`, and loses its
89
+ * binding when no entity inherits it. The input is never mutated.
90
+ *
91
+ * Throws, naming the profile, when the profile repeats a name, leaves a field
92
+ * entry with nothing to bind, both binds and excludes a field, puts "*" inside
93
+ * a list, names something the model does not declare, binds an abstract
94
+ * entity, states one of a relationship's join column lists without the
95
+ * other or gives both lists with different lengths, gives a source or
96
+ * expression the loader's readers reject, or is applied to a model that
97
+ * declares a `sql` executor, which belongs in each profile.
98
+ */
99
+ export declare function applyProfile(model: SemanticModel, profile: ProfileSpec, opts?: Omit<ApplyProfileOptions, 'from'>): SemanticModel;
100
+ /**
101
+ * Reads the profile file `text` and applies it to `model`, or returns the
102
+ * error that stops it, naming the profile. The warnings are those of the
103
+ * model file's load (`opts.from`), less the ones about values the profile
104
+ * replaces, followed by those for the values it sets, each prefixed with the
105
+ * document name.
106
+ */
107
+ export declare function applyProfileFile(model: SemanticModel, text: string, profileName: string, opts?: Omit<ApplyProfileOptions, 'warnings'>): {
108
+ model: SemanticModel;
109
+ warnings: string[];
110
+ } | {
111
+ error: string;
112
+ };
113
+ /** A profile applied to a model document loaded on its own. */
114
+ export interface AppliedProfile {
115
+ model: SemanticModel;
116
+ warnings: string[];
117
+ }
118
+ /**
119
+ * A profile a model declares: the text of a profile file, or a profile already
120
+ * read, such as one a vanilla model carries in its GOOGLE block. A profile
121
+ * already read may carry `label`, the file that holds it, which
122
+ * namedProfileErrors names in place of the model document.
123
+ */
124
+ export type DeclaredProfile = {
125
+ name: string;
126
+ text: string;
127
+ } | {
128
+ name: string;
129
+ spec: ProfileSpec;
130
+ label?: string;
131
+ };
132
+ export type DeclaredProfiles = (doc: {
133
+ name: string;
134
+ text: string;
135
+ }) => DeclaredProfile[];
136
+ export type ProfileFiles = (model: string) => Array<{
137
+ name: string;
138
+ text: string;
139
+ fileName: string;
140
+ }>;
141
+ /**
142
+ * The profiles the model document `doc` declares: `files`, its profile files,
143
+ * then the profiles a vanilla model carries in its GOOGLE block, by name, as
144
+ * the layout orders profile files. Push, `kcmd profiles` and the runtime all
145
+ * read them here, so they agree. Throws SemanticModelError, naming the
146
+ * document as a load does, when googleBlockProfiles throws, such as on block
147
+ * profiles a load rejects.
148
+ */
149
+ export declare function declaredProfiles(doc: {
150
+ name: string;
151
+ text: string;
152
+ }, files: Array<{
153
+ name: string;
154
+ text: string;
155
+ }>): DeclaredProfile[];
156
+ /** A profile applied to one model document. */
157
+ export type DocumentProfile = ({
158
+ kind: 'file';
159
+ } & AppliedProfile) | {
160
+ kind: 'error';
161
+ error: string;
162
+ stage: 'load' | 'apply';
163
+ } | {
164
+ kind: 'missing';
165
+ declared: string[];
166
+ };
167
+ /**
168
+ * Finds the profile `profileName` among `profiles`, the ones the model
169
+ * document `doc` declares, and applies it, as push, `kcmd profiles` and the
170
+ * runtime do. It applies to the document loaded on its own.
171
+ */
172
+ export declare function profileForDocument(doc: {
173
+ name: string;
174
+ text: string;
175
+ }, profiles: DeclaredProfile[], profileName: string, load?: DialectLoad): DocumentProfile;
176
+ /**
177
+ * The warnings that applying the profile `profileName` among `profiles` to the
178
+ * model document `doc` gives, each labeled with the profile. The warnings in
179
+ * `own`, which the document's own load gives, are left out. Empty unless
180
+ * profileForDocument applies the profile as a profile file.
181
+ */
182
+ export declare function appliedProfileWarnings(doc: {
183
+ name: string;
184
+ text: string;
185
+ }, profiles: DeclaredProfile[], profileName: string, own: string[], load?: DialectLoad): string[];
186
+ /**
187
+ * The error a command gives when a profile cannot be applied to the model
188
+ * document `docName`, whose profile files are `profileFiles`. An error applying
189
+ * a profile file names that file, as the profile checks do. An error applying
190
+ * a profile from a GOOGLE block names the model document.
191
+ */
192
+ export declare function profileErrorMessage(docName: string, res: Extract<DocumentProfile, {
193
+ kind: 'error';
194
+ }>, profileName: string, profileFiles: Array<{
195
+ name: string;
196
+ text: string;
197
+ fileName: string;
198
+ }>): string;
199
+ /**
200
+ * Why `model` cannot be used because a deployment names a binding profile
201
+ * `declared` does not hold, or undefined when each names `default` or a
202
+ * declared profile. `document` is the model's document, for the message.
203
+ */
204
+ export declare function undeclaredProfileError(document: string, model: SemanticModel, declared: string[]): string | undefined;
205
+ /** The error for a profile name a model document does not declare. */
206
+ export declare function unknownProfileMessage(profileName: string, model: string, declared: string[]): string;
207
+ /**
208
+ * The error for a model document `docName` whose `profiles` include one named
209
+ * `default` in any letter case. Push refuses every graph push of such a
210
+ * model, and the runtime refuses the model too. Undefined when no profile has
211
+ * that name.
212
+ */
213
+ export declare function reservedProfileNameError(docName: string, profiles: {
214
+ name: string;
215
+ }[]): string | undefined;
216
+ export declare function readerMessage(err: SemanticModelError): string;
217
+ export declare function profileSpecOf(doc: any, profileName: string): ProfileSpec;
218
+ export declare function expressionSources(f: Pick<Field, 'expression' | 'dialects' | 'importedExpression' | 'importedDialect'>): Array<{
219
+ text: string;
220
+ dialect: string;
221
+ }>;
222
+ declare function textsFor(f: Pick<Field, 'expression' | 'dialects' | 'importedExpression' | 'importedDialect'>, dialect: string | null | undefined): Array<{
223
+ text: string;
224
+ dialect: string;
225
+ }>;
226
+ export declare function noTextFor(f: Parameters<typeof textsFor>[0], dialects: string[]): boolean;
227
+ export interface TableDialects {
228
+ name: string;
229
+ dialects: string[];
230
+ unread?: boolean;
231
+ guessed?: boolean;
232
+ }
233
+ export declare function tableDialects(entity: Pick<Entity, 'abstract' | 'authoredSource' | 'dataSource' | 'sourceDialect'>, withUnread: boolean): TableDialects | undefined;
234
+ export declare function fieldsOnTables(model: SemanticModel, withUnread?: boolean): Array<{
235
+ entity: Entity;
236
+ field: Field;
237
+ table: TableDialects;
238
+ }>;
239
+ export declare function importCommand(flags: string, version?: string): string;
240
+ export declare function importTranspiles(f: Parameters<typeof textsFor>[0], dialect: string | undefined, model: Pick<SemanticModel, 'entities'>): boolean;
241
+ export declare function importHint(f: Parameters<typeof textsFor>[0], dialect: string, model: Pick<SemanticModel, 'entities' | 'version'>): string;
242
+ export declare function sourcesDialect(model: {
243
+ entities?: ReadonlyArray<Pick<Entity, 'abstract' | 'authoredSource' | 'dataSource' | 'sourceDialect'>>;
244
+ }): string | null | undefined;
245
+ /** What the caller of validateProfileCompleteness checks itself. */
246
+ export interface CompletenessOptions extends DialectLoad {
247
+ ownSqlOnly?: {
248
+ dialect?: string | null;
249
+ };
250
+ }
251
+ /**
252
+ * Returns one message per reason `profile` cannot be deployed against
253
+ * `baseModel`, or an empty list. Checks a single profile: nothing unknown,
254
+ * every field it binds holding SQL on each entity that inherits it, every
255
+ * concrete entity bound and all in one database, its own keys where the model
256
+ * file states keys, every relationship bound, every field the model file
257
+ * leaves unbound accounted for, every field whose expression reads a column
258
+ * restated or excluded, and exclusions closed under dependency, metrics
259
+ * included. The check that join columns cover a key of the target is in
260
+ * validateProfileConsistency. An expression the SQL parser cannot read
261
+ * is skipped by the rules that read references, with a message in `warnings`.
262
+ * `opts` says what the caller checks itself, so that nothing is reported
263
+ * twice.
264
+ */
265
+ export declare function validateProfileCompleteness(baseModel: SemanticModel, profile: ProfileSpec, warnings?: string[], opts?: CompletenessOptions): string[];
266
+ /**
267
+ * Returns one message for each binding, the model file's included, whose
268
+ * `to_columns` for a relationship cover no primary or unique key of its
269
+ * target, or an empty list. A superset of a key covers it (see coversKey).
270
+ * Keys and join columns are compared as written. A binding whose key or join
271
+ * columns are not all column names (isColumnName) is left out. `applied` names
272
+ * the profile the push applies. The "covers no key" check then leaves out that
273
+ * profile's join, or the model file's when the profile states none.
274
+ */
275
+ export declare function validateProfileConsistency(baseModel: SemanticModel, profiles: ProfileSpec[], applied?: string): string[];
276
+ export interface LocalCheckError {
277
+ error: string;
278
+ document?: string;
279
+ profile?: string;
280
+ }
281
+ export declare function namedProfileErrors(docs: Array<{
282
+ name: string;
283
+ text: string;
284
+ }>, baseByDoc: Map<string, SemanticModel>, profileDocs: ProfileFiles, declared: DeclaredProfiles, warnings: string[], load: DialectLoad): LocalCheckError[];
285
+ export declare function inheritanceErrorOf(model: SemanticModel): string | undefined;
286
+ export declare function columnBinding(model: SemanticModel, entityName: string, field: string, dialect?: string | null): {
287
+ owner: string;
288
+ column: string;
289
+ } | undefined;
290
+ export declare function statesKey(primaryKey: string[] | undefined, uniqueKeys: string[][] | undefined): boolean;
291
+ export {};