@malloydata/malloy 0.0.428 → 0.0.429

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 (42) hide show
  1. package/dist/api/foundation/build_targets.d.ts +50 -0
  2. package/dist/api/foundation/build_targets.js +221 -0
  3. package/dist/api/foundation/core.d.ts +54 -10
  4. package/dist/api/foundation/core.js +80 -17
  5. package/dist/api/foundation/index.d.ts +1 -1
  6. package/dist/api/foundation/runtime.d.ts +18 -1
  7. package/dist/api/foundation/runtime.js +35 -0
  8. package/dist/api/foundation/types.d.ts +93 -6
  9. package/dist/index.d.ts +1 -1
  10. package/dist/lang/ast/field-space/dynamic-space.js +20 -3
  11. package/dist/lang/ast/query-elements/query-arrow.d.ts +1 -1
  12. package/dist/lang/ast/query-elements/query-arrow.js +10 -4
  13. package/dist/lang/ast/query-elements/query-base.d.ts +1 -1
  14. package/dist/lang/ast/query-elements/query-base.js +1 -6
  15. package/dist/lang/ast/query-elements/query-raw.d.ts +1 -1
  16. package/dist/lang/ast/query-elements/query-raw.js +4 -2
  17. package/dist/lang/ast/query-elements/query-reference.d.ts +1 -1
  18. package/dist/lang/ast/query-elements/query-reference.js +9 -3
  19. package/dist/lang/ast/query-elements/query-refine.d.ts +1 -1
  20. package/dist/lang/ast/query-elements/query-refine.js +10 -4
  21. package/dist/lang/ast/source-elements/query-source.js +1 -1
  22. package/dist/lang/ast/source-elements/sql-source.js +2 -2
  23. package/dist/lang/ast/source-query-elements/source-query-element.d.ts +27 -0
  24. package/dist/lang/ast/source-query-elements/source-query-element.js +33 -0
  25. package/dist/lang/ast/source-query-elements/sq-arrow.d.ts +2 -0
  26. package/dist/lang/ast/source-query-elements/sq-arrow.js +12 -6
  27. package/dist/lang/ast/source-query-elements/sq-refine.d.ts +2 -0
  28. package/dist/lang/ast/source-query-elements/sq-refine.js +12 -6
  29. package/dist/lang/ast/source-query-elements/sq-source.d.ts +1 -0
  30. package/dist/lang/ast/source-query-elements/sq-source.js +6 -3
  31. package/dist/lang/ast/sql-elements/sql-string.d.ts +1 -1
  32. package/dist/lang/ast/sql-elements/sql-string.js +25 -5
  33. package/dist/lang/ast/statements/import-statement.js +7 -10
  34. package/dist/lang/ast/types/query-element.d.ts +15 -1
  35. package/dist/lang/parse-log.d.ts +2 -0
  36. package/dist/lang/test/test-translator.d.ts +17 -0
  37. package/dist/lang/test/test-translator.js +46 -2
  38. package/dist/model/persist_utils.d.ts +61 -10
  39. package/dist/model/persist_utils.js +172 -63
  40. package/dist/version.d.ts +1 -1
  41. package/dist/version.js +1 -1
  42. package/package.json +4 -4
@@ -0,0 +1,50 @@
1
+ import type { LogMessage } from '../../lang';
2
+ import type { PersistNode } from '../../model/persist_utils';
3
+ import type { Model, PersistSource } from './core';
4
+ import type { ConnectionBuild } from './types';
5
+ /**
6
+ * A walk node with its source already resolved.
7
+ *
8
+ * "Is this node a table, and which one" is decided here, once. Deciding it
9
+ * twice — once to collect connection digests and once to fold — means two
10
+ * predicates that have to agree, and a disagreement shows up as a digest
11
+ * missing for a connection that was right there.
12
+ */
13
+ export interface ResolvedNode {
14
+ node: PersistNode;
15
+ /** The table this node builds, absent if it is a route or cannot resolve */
16
+ source: PersistSource | undefined;
17
+ }
18
+ /**
19
+ * Walk the model and resolve each node's source in one pass.
20
+ *
21
+ * Materializing is not laziness lost: the fold needs a connection digest per
22
+ * source and fetching those is async, so the walk has to be crossed twice.
23
+ */
24
+ export declare function resolvePersistWalk(model: Model, tagParseLog: LogMessage[]): ResolvedNode[];
25
+ /**
26
+ * Fold a walk into the artifacts it produces, grouped by connection and in
27
+ * dependency order.
28
+ *
29
+ * The walk emits persistable *sources*; a builder needs *tables*, and several
30
+ * sources routinely land on one (see `BuildTarget`). This merges them by
31
+ * BuildID, keeping every source that mapped onto a target in `target.sources`.
32
+ *
33
+ * One pass suffices because the walk is in dependency order: by the time a
34
+ * source arrives, everything it references has already been placed, so its
35
+ * edges can be written immediately. A source that is not itself a table
36
+ * contributes its children's targets to whoever referenced it, which is how an
37
+ * edge survives a route.
38
+ *
39
+ * Targets come back in dependency order, but no coarser schedule than that —
40
+ * a builder wanting concurrency reads `dependsOn` and starts each target when
41
+ * its own dependencies finish, which waits on strictly less than batching by
42
+ * depth would.
43
+ *
44
+ * Separate from `Runtime.getBuildTargets` because given the digests it is
45
+ * pure, and can be tested without a connection.
46
+ *
47
+ * @param walk A resolved walk from {@link resolvePersistWalk}, in order
48
+ * @param connectionDigests One digest per connection named by a persist source
49
+ */
50
+ export declare function mkBuildTargets(walk: ResolvedNode[], connectionDigests: Record<string, string>): ConnectionBuild[];
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.resolvePersistWalk = resolvePersistWalk;
8
+ exports.mkBuildTargets = mkBuildTargets;
9
+ /**
10
+ * Read a key this function's own construction guarantees is present.
11
+ *
12
+ * Every key here came out of `targets`, which is fully built before anything
13
+ * reads it back. Saying that once beats five bare `!`s that each look like a
14
+ * claim needing checking.
15
+ */
16
+ function mustGet(map, key) {
17
+ const value = map.get(key);
18
+ if (value === undefined) {
19
+ throw new Error('build target ' + String(key) + ' vanished between passes');
20
+ }
21
+ return value;
22
+ }
23
+ /**
24
+ * Walk the model and resolve each node's source in one pass.
25
+ *
26
+ * Materializing is not laziness lost: the fold needs a connection digest per
27
+ * source and fetching those is async, so the walk has to be crossed twice.
28
+ */
29
+ function resolvePersistWalk(model, tagParseLog) {
30
+ const resolved = [];
31
+ for (const node of model._walkPersistSources(tagParseLog)) {
32
+ resolved.push({
33
+ node,
34
+ source: node.persistent
35
+ ? model._persistSourceFor(node.sourceID)
36
+ : undefined,
37
+ });
38
+ }
39
+ return resolved;
40
+ }
41
+ /** A BuildID is a hex digest, so it cannot contain the separator. */
42
+ function targetKey(connectionName, buildId) {
43
+ return `${connectionName}:${buildId}`;
44
+ }
45
+ /** Append without repeating. */
46
+ function addUnique(into, seen, keys) {
47
+ for (const key of keys) {
48
+ if (!seen.has(key)) {
49
+ seen.add(key);
50
+ into.push(key);
51
+ }
52
+ }
53
+ }
54
+ /**
55
+ * Fold a walk into the artifacts it produces, grouped by connection and in
56
+ * dependency order.
57
+ *
58
+ * The walk emits persistable *sources*; a builder needs *tables*, and several
59
+ * sources routinely land on one (see `BuildTarget`). This merges them by
60
+ * BuildID, keeping every source that mapped onto a target in `target.sources`.
61
+ *
62
+ * One pass suffices because the walk is in dependency order: by the time a
63
+ * source arrives, everything it references has already been placed, so its
64
+ * edges can be written immediately. A source that is not itself a table
65
+ * contributes its children's targets to whoever referenced it, which is how an
66
+ * edge survives a route.
67
+ *
68
+ * Targets come back in dependency order, but no coarser schedule than that —
69
+ * a builder wanting concurrency reads `dependsOn` and starts each target when
70
+ * its own dependencies finish, which waits on strictly less than batching by
71
+ * depth would.
72
+ *
73
+ * Separate from `Runtime.getBuildTargets` because given the digests it is
74
+ * pure, and can be tested without a connection.
75
+ *
76
+ * @param walk A resolved walk from {@link resolvePersistWalk}, in order
77
+ * @param connectionDigests One digest per connection named by a persist source
78
+ */
79
+ function mkBuildTargets(walk, connectionDigests) {
80
+ var _a;
81
+ const targets = new Map();
82
+ // What each source hands to whoever referenced it: its own target if it is
83
+ // one, otherwise whatever it was a route to.
84
+ const contributes = new Map();
85
+ for (const { node, source } of walk) {
86
+ const childKeys = [];
87
+ const seen = new Set();
88
+ for (const dep of node.dependsOn) {
89
+ addUnique(childKeys, seen, (_a = contributes.get(dep)) !== null && _a !== void 0 ? _a : []);
90
+ }
91
+ if (source === undefined) {
92
+ // A route, or a source whose definition could not be resolved. Either
93
+ // way it is not a table; pass its dependencies up.
94
+ contributes.set(node.sourceID, childKeys);
95
+ continue;
96
+ }
97
+ const connectionName = source.connectionName;
98
+ const digest = connectionDigests[connectionName];
99
+ if (digest === undefined) {
100
+ throw new Error(`No connection digest for '${connectionName}', needed to compute the ` +
101
+ `BuildID of '${node.sourceID}'. Supply a digest for every connection ` +
102
+ 'named by a persist source.');
103
+ }
104
+ const sql = source.getSQL();
105
+ const buildId = source.makeBuildId(digest, sql);
106
+ const key = targetKey(connectionName, buildId);
107
+ let target = targets.get(key);
108
+ if (target === undefined) {
109
+ target = {
110
+ buildId,
111
+ connectionName,
112
+ sql,
113
+ sources: [],
114
+ dependsOn: new Set(),
115
+ };
116
+ targets.set(key, target);
117
+ }
118
+ const mine = new Set();
119
+ for (const childKey of childKeys) {
120
+ // A child on my own key is an extension of me — the same table twice,
121
+ // not a dependency.
122
+ if (childKey === key)
123
+ continue;
124
+ mine.add(childKey);
125
+ }
126
+ // Intersect, do not union. Union makes cycles: `source: alias is base
127
+ // extend { join_one: mid }` where `mid` reads `base` merges alias onto
128
+ // base's target carrying an edge to `mid`, while `mid` depends on `base`.
129
+ // Only one of those two edges is real.
130
+ //
131
+ // Intersecting keeps the real ones because of the six-paths list at
132
+ // `walkPersistentDependencies`: those are every way one source's SQL can
133
+ // incorporate another's — except `CompositeSourceDef.sources[]`, which the
134
+ // walk skips, and which is why composites and persistence are documented
135
+ // as incompatible. So a source that compiles to this target's SQL
136
+ // necessarily reaches everything that SQL inlines: every set here is a
137
+ // superset of the real dependencies, and what differs between them is only
138
+ // the join over-approximation, which is what the intersection removes.
139
+ //
140
+ // The safety therefore rests on that list staying complete, composites
141
+ // being its one known and deliberate hole. A path added to the IR and not
142
+ // to the walk — or composites being made to work with persistence without
143
+ // revisiting this — would break it, and the symptom here is quieter than a
144
+ // wrong plan: a table built from inlined SQL instead of reading the one
145
+ // below it. No error, just the expensive query persistence exists to
146
+ // avoid.
147
+ if (target.sources.length === 0) {
148
+ target.dependsOn = mine;
149
+ }
150
+ else {
151
+ for (const had of [...target.dependsOn]) {
152
+ if (!mine.has(had))
153
+ target.dependsOn.delete(had);
154
+ }
155
+ }
156
+ target.sources.push(source);
157
+ contributes.set(node.sourceID, [key]);
158
+ }
159
+ // Every target exists before any `dependsOn` is filled in, so references can
160
+ // point both ways round a diamond.
161
+ const built = new Map();
162
+ for (const [key, partial] of targets) {
163
+ built.set(key, {
164
+ buildId: partial.buildId,
165
+ connectionName: partial.connectionName,
166
+ sql: partial.sql,
167
+ sources: partial.sources,
168
+ dependsOn: [],
169
+ });
170
+ }
171
+ for (const [key, partial] of targets) {
172
+ const target = mustGet(built, key);
173
+ for (const depKey of partial.dependsOn) {
174
+ target.dependsOn.push(mustGet(built, depKey));
175
+ }
176
+ }
177
+ // Emit dependencies before dependents. Depth-first with a memo: a target is
178
+ // appended once, after everything it reads.
179
+ const byConnection = new Map();
180
+ const placed = new Set();
181
+ const openKeys = new Set();
182
+ const place = (key) => {
183
+ if (placed.has(key))
184
+ return;
185
+ if (openKeys.has(key)) {
186
+ // A real edge cannot make a cycle: a target's SQL contains its
187
+ // dependencies' SQL inline, so two targets cannot each contain the
188
+ // other. But an edge here is not always a real one — a source merging
189
+ // onto a target can bring a join the target's SQL never mentions, and
190
+ // that is what intersecting the merged sources' edges above removes. If
191
+ // one gets through anyway, say so rather than hang.
192
+ const names = mustGet(targets, key)
193
+ .sources.map(s => s.sourceID)
194
+ .join(', ');
195
+ throw new Error(`Cycle in the persistence build graph at: ${names}`);
196
+ }
197
+ openKeys.add(key);
198
+ for (const depKey of mustGet(targets, key).dependsOn) {
199
+ place(depKey);
200
+ }
201
+ openKeys.delete(key);
202
+ placed.add(key);
203
+ const target = mustGet(built, key);
204
+ const forConnection = byConnection.get(target.connectionName);
205
+ if (forConnection === undefined) {
206
+ byConnection.set(target.connectionName, [target]);
207
+ }
208
+ else {
209
+ forConnection.push(target);
210
+ }
211
+ };
212
+ for (const key of targets.keys()) {
213
+ place(key);
214
+ }
215
+ const connections = [];
216
+ for (const [connectionName, connectionTargets] of byConnection) {
217
+ connections.push({ connectionName, targets: connectionTargets });
218
+ }
219
+ return connections;
220
+ }
221
+ //# sourceMappingURL=build_targets.js.map
@@ -3,6 +3,7 @@ import type { BuildID, CompiledQuery, ConstantExpr, DocumentLocation, BooleanFie
3
3
  import { QueryModel } from '../../model';
4
4
  import type { Dialect } from '../../dialect';
5
5
  import type { BuildGraph, CompileQueryOptions } from './types';
6
+ import type { PersistWalk } from '../../model/persist_utils';
6
7
  import { Tag } from '@malloydata/malloy-tag';
7
8
  import type { MalloyTagParse, TagParseSpec } from './annotation';
8
9
  import { Annotations } from './annotation';
@@ -464,19 +465,44 @@ export declare class Model implements Taggable {
464
465
  get exportedExplores(): Explore[];
465
466
  get _modelDef(): ModelDef;
466
467
  /**
467
- * Get the build plan for all #@ persist sources.
468
+ * Require the `experimental.persistence` compiler flag.
468
469
  *
469
- * Walks through ALL queries and sources in the model, finding any persistent
470
- * dependencies they reference (including hidden dependencies from imports).
470
+ * Read off the resolved model annotations (`.modelAnnotations`, the
471
+ * import/extend fold) rather than this model's own `##`, so the flag carries
472
+ * across extend.
473
+ */
474
+ private requirePersistence;
475
+ /**
476
+ * Walk every persistable source this model reaches, in dependency order.
477
+ *
478
+ * The roots are every source and query the model names, plus its unnamed
479
+ * queries. They share one walk, so a source several of them reach is visited
480
+ * once.
481
+ *
482
+ * This is the raw material {@link Runtime.getBuildTargets} folds into
483
+ * tables. Nothing here is keyed by artifact: that needs a BuildID, and a
484
+ * model cannot reach a connection to compute one.
485
+ */
486
+ _walkPersistSources(tagParseLog: LogMessage[]): PersistWalk;
487
+ /**
488
+ * The {@link PersistSource} for a sourceID, or undefined if this model
489
+ * cannot resolve it.
490
+ */
491
+ _persistSourceFor(sourceID: string): PersistSource | undefined;
492
+ /**
493
+ * Every `#@ persist` source in the model, as a graph keyed by sourceID.
471
494
  *
472
- * Returns a BuildPlan containing:
473
- * - `graphs`: Build graphs for root sources only (minimal build set)
474
- * - `sources`: Map from sourceId to PersistSource (all persist sources)
495
+ * Walks all queries and sources, including dependencies that arrived through
496
+ * an import and are in no namespace here, and returns the *roots* — the
497
+ * sources nothing else depends on each carrying its dependencies in
498
+ * `dependsOn`.
475
499
  *
476
- * The minimal build set contains only "root" sources - those not depended
477
- * on by any other persist source. Each root includes its transitive
478
- * dependencies in the dependsOn field, preserving the tree structure
479
- * for parallel building.
500
+ * @deprecated A builder wants tables, and this reports sources. Those are not
501
+ * one to one: several sources routinely map onto one table, so keying on
502
+ * sourceID leaves every builder to discover the mapping by hashing. Its roots
503
+ * are computed by sourceID too, so for a persisted source with an extension
504
+ * the root is the extension and the declaring source is never named. Use
505
+ * {@link Runtime.getBuildTargets}, which answers in tables.
480
506
  *
481
507
  * @return BuildPlan with graphs and sources map
482
508
  */
@@ -538,6 +564,24 @@ export declare class PersistSource implements Taggable {
538
564
  get annotations(): Annotations;
539
565
  /** The model annotations resolved for this source. */
540
566
  get modelAnnotations(): Annotations;
567
+ /**
568
+ * Where this source was declared: the URL of the model that declared it, and
569
+ * the range of the `source:` statement.
570
+ *
571
+ * This is the handle to report a build failure against. A name is ambiguous
572
+ * across models, a sourceID is a name and a URL glued together, and a BuildID
573
+ * is a hash — none of them answer "where do I go to fix this," and a location
574
+ * does.
575
+ *
576
+ * The URL is whatever the model was loaded from, which may be a scheme only
577
+ * the caller understands. Rendering it for a human is the builder's job for
578
+ * the same reason `name=` is: the core supplied no URLReader and has no idea
579
+ * what these URLs mean.
580
+ *
581
+ * Undefined for a source with no recorded position — one synthesized rather
582
+ * than written down.
583
+ */
584
+ get location(): DocumentLocation | undefined;
541
585
  /**
542
586
  * The connection name for this source.
543
587
  */
@@ -1107,30 +1107,69 @@ class Model {
1107
1107
  return this.modelDef;
1108
1108
  }
1109
1109
  /**
1110
- * Get the build plan for all #@ persist sources.
1110
+ * Require the `experimental.persistence` compiler flag.
1111
1111
  *
1112
- * Walks through ALL queries and sources in the model, finding any persistent
1113
- * dependencies they reference (including hidden dependencies from imports).
1112
+ * Read off the resolved model annotations (`.modelAnnotations`, the
1113
+ * import/extend fold) rather than this model's own `##`, so the flag carries
1114
+ * across extend.
1115
+ */
1116
+ requirePersistence(api) {
1117
+ const modelTag = this.modelAnnotations.parseAsTag('!').tag;
1118
+ if (!modelTag.has('experimental', 'persistence')) {
1119
+ throw new Error(`Model must have ##! experimental.persistence to use ${api}`);
1120
+ }
1121
+ }
1122
+ /**
1123
+ * Walk every persistable source this model reaches, in dependency order.
1114
1124
  *
1115
- * Returns a BuildPlan containing:
1116
- * - `graphs`: Build graphs for root sources only (minimal build set)
1117
- * - `sources`: Map from sourceId to PersistSource (all persist sources)
1125
+ * The roots are every source and query the model names, plus its unnamed
1126
+ * queries. They share one walk, so a source several of them reach is visited
1127
+ * once.
1118
1128
  *
1119
- * The minimal build set contains only "root" sources - those not depended
1120
- * on by any other persist source. Each root includes its transitive
1121
- * dependencies in the dependsOn field, preserving the tree structure
1122
- * for parallel building.
1129
+ * This is the raw material {@link Runtime.getBuildTargets} folds into
1130
+ * tables. Nothing here is keyed by artifact: that needs a BuildID, and a
1131
+ * model cannot reach a connection to compute one.
1132
+ */
1133
+ _walkPersistSources(tagParseLog) {
1134
+ this.requirePersistence('_walkPersistSources()');
1135
+ const roots = [];
1136
+ for (const obj of Object.values(this.modelDef.contents)) {
1137
+ if (obj.type === 'query' || (0, model_1.isSourceDef)(obj)) {
1138
+ roots.push(obj);
1139
+ }
1140
+ }
1141
+ roots.push(...this.modelDef.queryList);
1142
+ return (0, persist_utils_1.walkPersistentDependencies)(roots, this.modelDef, tagParseLog);
1143
+ }
1144
+ /**
1145
+ * The {@link PersistSource} for a sourceID, or undefined if this model
1146
+ * cannot resolve it.
1147
+ */
1148
+ _persistSourceFor(sourceID) {
1149
+ const sourceDef = (0, source_def_utils_1.resolveSourceID)(this.modelDef, sourceID);
1150
+ return sourceDef
1151
+ ? new PersistSource(new Explore(this.modelDef, sourceDef), this)
1152
+ : undefined;
1153
+ }
1154
+ /**
1155
+ * Every `#@ persist` source in the model, as a graph keyed by sourceID.
1156
+ *
1157
+ * Walks all queries and sources, including dependencies that arrived through
1158
+ * an import and are in no namespace here, and returns the *roots* — the
1159
+ * sources nothing else depends on — each carrying its dependencies in
1160
+ * `dependsOn`.
1161
+ *
1162
+ * @deprecated A builder wants tables, and this reports sources. Those are not
1163
+ * one to one: several sources routinely map onto one table, so keying on
1164
+ * sourceID leaves every builder to discover the mapping by hashing. Its roots
1165
+ * are computed by sourceID too, so for a persisted source with an extension
1166
+ * the root is the extension and the declaring source is never named. Use
1167
+ * {@link Runtime.getBuildTargets}, which answers in tables.
1123
1168
  *
1124
1169
  * @return BuildPlan with graphs and sources map
1125
1170
  */
1126
1171
  getBuildPlan() {
1127
- // Require experimental.persistence compiler flag. Read the resolved model
1128
- // annotations (`.modelAnnotations`, the import/extend fold) rather than this
1129
- // model's own `##` so the flag carries across extend.
1130
- const modelTag = this.modelAnnotations.parseAsTag('!').tag;
1131
- if (!modelTag.has('experimental', 'persistence')) {
1132
- throw new Error('Model must have ##! experimental.persistence to use getBuildPlan()');
1133
- }
1172
+ this.requirePersistence('getBuildPlan()');
1134
1173
  const allDeps = [];
1135
1174
  const tagParseLog = [];
1136
1175
  // Walk all objects in the model to find persistent dependencies
@@ -1150,8 +1189,12 @@ class Model {
1150
1189
  const rootNodes = (0, persist_utils_1.minimalBuildGraph)(allDeps);
1151
1190
  // Build the sources map from all persistent sourceIDs encountered
1152
1191
  const sourcesMap = {};
1192
+ const seen = new Set();
1153
1193
  const collectSources = (nodes) => {
1154
1194
  for (const node of nodes) {
1195
+ if (seen.has(node))
1196
+ continue;
1197
+ seen.add(node);
1155
1198
  if (!(node.sourceID in sourcesMap)) {
1156
1199
  const sourceDef = (0, source_def_utils_1.resolveSourceID)(this.modelDef, node.sourceID);
1157
1200
  if (sourceDef) {
@@ -1257,6 +1300,26 @@ class PersistSource {
1257
1300
  get modelAnnotations() {
1258
1301
  return this.explore.modelAnnotations;
1259
1302
  }
1303
+ /**
1304
+ * Where this source was declared: the URL of the model that declared it, and
1305
+ * the range of the `source:` statement.
1306
+ *
1307
+ * This is the handle to report a build failure against. A name is ambiguous
1308
+ * across models, a sourceID is a name and a URL glued together, and a BuildID
1309
+ * is a hash — none of them answer "where do I go to fix this," and a location
1310
+ * does.
1311
+ *
1312
+ * The URL is whatever the model was loaded from, which may be a scheme only
1313
+ * the caller understands. Rendering it for a human is the builder's job for
1314
+ * the same reason `name=` is: the core supplied no URLReader and has no idea
1315
+ * what these URLs mean.
1316
+ *
1317
+ * Undefined for a source with no recorded position — one synthesized rather
1318
+ * than written down.
1319
+ */
1320
+ get location() {
1321
+ return this.persistableDef.location;
1322
+ }
1260
1323
  /**
1261
1324
  * The connection name for this source.
1262
1325
  */
@@ -1,4 +1,4 @@
1
- export type { Taggable, Loggable, ParseOptions, CompileOptions, CompileQueryOptions, BuildNode, BuildGraph, } from './types';
1
+ export type { Taggable, Loggable, ParseOptions, CompileOptions, CompileQueryOptions, BuildNode, BuildGraph, BuildTarget, BuildTargets, ConnectionBuild, } from './types';
2
2
  export { EMPTY_BUILD_MANIFEST } from './types';
3
3
  export { EmptyURLReader, InMemoryURLReader, FixedConnectionMap, hashForInvalidationKey, isInternalURL, readURL, getInvalidationKey, } from './readers';
4
4
  export type { ModelCache, CachedModel } from './cache';
@@ -6,7 +6,7 @@ import type { Dialect } from '../../dialect';
6
6
  import type { RunSQLOptions } from '../../run_sql_options';
7
7
  import type { CacheManager } from './cache';
8
8
  import type { MalloyConfig } from './config';
9
- import type { ParseOptions, CompileOptions, CompileQueryOptions } from './types';
9
+ import type { ParseOptions, CompileOptions, CompileQueryOptions, BuildTargets } from './types';
10
10
  import type { PreparedResult, Explore } from './core';
11
11
  import { Model, PreparedQuery } from './core';
12
12
  import type { DataRecord, Result } from './result';
@@ -285,6 +285,23 @@ export declare class Runtime {
285
285
  * @return A promise of a compiled `PreparedQuery`.
286
286
  */
287
287
  getQueryByName(model: ModelURL | ModelString, name: string, options?: ParseOptions & CompileOptions): Promise<PreparedQuery>;
288
+ /**
289
+ * What a builder has to build for this model, and in what order.
290
+ *
291
+ * This is the builder entry point. A model can only enumerate persistable
292
+ * *sources*, which is not the same set as the tables they produce — several
293
+ * sources routinely map onto one table, and only a connection digest can
294
+ * tell you which. That is why this lives on Runtime: it holds the
295
+ * connections, so it can finish the answer a model can only start.
296
+ *
297
+ * Connections are independent of one another; within one, targets come back
298
+ * in dependency order and each carries its own `dependsOn`. See
299
+ * {@link BuildTargets}.
300
+ *
301
+ * @param model A compiled model with `##! experimental.persistence`
302
+ * @return The targets to build, with any annotation parse messages
303
+ */
304
+ getBuildTargets(model: Model): Promise<BuildTargets>;
288
305
  }
289
306
  export declare class ConnectionRuntime extends Runtime {
290
307
  readonly rawConnections: Connection[];
@@ -11,6 +11,7 @@ const validate_table_path_1 = require("../../connection/validate_table_path");
11
11
  const config_1 = require("./config");
12
12
  const row_data_utils_1 = require("../../api/row_data_utils");
13
13
  const readers_1 = require("./readers");
14
+ const build_targets_1 = require("./build_targets");
14
15
  const core_1 = require("./core");
15
16
  const compile_1 = require("./compile");
16
17
  // =============================================================================
@@ -480,6 +481,40 @@ class Runtime {
480
481
  getQueryByName(model, name, options) {
481
482
  return this.loadQueryByName(model, name, options).getPreparedQuery();
482
483
  }
484
+ /**
485
+ * What a builder has to build for this model, and in what order.
486
+ *
487
+ * This is the builder entry point. A model can only enumerate persistable
488
+ * *sources*, which is not the same set as the tables they produce — several
489
+ * sources routinely map onto one table, and only a connection digest can
490
+ * tell you which. That is why this lives on Runtime: it holds the
491
+ * connections, so it can finish the answer a model can only start.
492
+ *
493
+ * Connections are independent of one another; within one, targets come back
494
+ * in dependency order and each carries its own `dependsOn`. See
495
+ * {@link BuildTargets}.
496
+ *
497
+ * @param model A compiled model with `##! experimental.persistence`
498
+ * @return The targets to build, with any annotation parse messages
499
+ */
500
+ async getBuildTargets(model) {
501
+ const tagParseLog = [];
502
+ const walk = (0, build_targets_1.resolvePersistWalk)(model, tagParseLog);
503
+ const connectionDigests = (0, model_1.mkSafeRecord)();
504
+ for (const { source } of walk) {
505
+ if (source === undefined)
506
+ continue;
507
+ const connectionName = source.connectionName;
508
+ if (!(connectionName in connectionDigests)) {
509
+ const connection = await this.connections.lookupConnection(connectionName);
510
+ connectionDigests[connectionName] = connection.getDigest();
511
+ }
512
+ }
513
+ return {
514
+ connections: (0, build_targets_1.mkBuildTargets)(walk, connectionDigests),
515
+ tagParseLog,
516
+ };
517
+ }
483
518
  }
484
519
  exports.Runtime = Runtime;
485
520
  // =============================================================================