@malloy-publisher/server 0.10.3 → 0.10.4

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.
@@ -7990,17 +7990,18 @@ components:
7990
7990
  BuildPlan:
7991
7991
  type: object
7992
7992
  description: |
7993
- The package's persist build plan. Mirrors Malloy's native build plan plus
7994
- the minimal per-source detail a caller needs to assign
7995
- identity/naming/realization. Lineage, policy, and connection capability
7996
- are intentionally omitted until they carry real data.
7993
+ The package's persist build plan, from Malloy's build targets: the order
7994
+ its persist sources build in, the tables each reads, and the minimal
7995
+ per-source detail a caller needs to assign identity/naming/realization.
7996
+ Policy and connection capability are intentionally omitted until they
7997
+ carry real data.
7997
7998
  required: [graphs, sources]
7998
7999
  properties:
7999
8000
  graphs:
8000
8001
  type: array
8001
8002
  description: >-
8002
8003
  Dependency-ordered build graphs. One per connection per model, not one
8003
- per connection: a model contributes a graph for each connection its ROOT
8004
+ per connection: a model contributes a graph for each connection its
8004
8005
  persist sources use, and a package's plan concatenates every model's, so
8005
8006
  the same source appears in the graph of every model that imports it.
8006
8007
  items:
@@ -8143,8 +8144,9 @@ components:
8143
8144
  nodes:
8144
8145
  type: array
8145
8146
  description:
8146
- Leveled build nodes; each inner array is one parallelizable level,
8147
- levels run in order.
8147
+ A single level holding every persist source of the graph, in build
8148
+ order. Each node's `dependsOn` names every source on every table it
8149
+ reads, so the edges are complete without walking anything.
8148
8150
  items:
8149
8151
  type: array
8150
8152
  items:
@@ -8159,7 +8161,11 @@ components:
8159
8161
  description: "sourceName@modelURL"
8160
8162
  dependsOn:
8161
8163
  type: array
8162
- description: Upstream sourceIDs in this graph.
8164
+ description: >-
8165
+ sourceIDs of the sources on the tables this source reads. Sources on
8166
+ one table (a source and its extensions) read none of each other. A
8167
+ source Malloy cannot plan, which is refused rather than built, lists
8168
+ none.
8163
8169
  items:
8164
8170
  type: string
8165
8171
 
@@ -164831,7 +164831,7 @@ var init_telemetry = __esm(() => {
164831
164831
  });
164832
164832
 
164833
164833
  // package.json
164834
- var version = "0.10.3";
164834
+ var version = "0.10.4";
164835
164835
  var init_package = () => {};
164836
164836
 
164837
164837
  // src/version.ts
@@ -22216,7 +22216,7 @@ async function compileSynthesizedPreaggregation(args) {
22216
22216
  const { runtime, importBaseURL } = await args.getRuntime(new Map([[synthesizedURL.href, text]]));
22217
22217
  const materializer = runtime.loadModel(synthesizedURL, { importBaseURL });
22218
22218
  const model = await materializer.getModel();
22219
- return { plans, model, materializer, text };
22219
+ return { plans, model, materializer, runtime, text };
22220
22220
  }
22221
22221
  async function tryCompileSynthesizedPreaggregation(args) {
22222
22222
  try {
@@ -23215,27 +23215,47 @@ function composeDeclaredQueryMetadata(layers) {
23215
23215
  const resolved = Object.assign({}, ...ordered);
23216
23216
  return Object.keys(resolved).length > 0 ? resolved : null;
23217
23217
  }
23218
- function flattenDependsOn(node) {
23219
- return node.dependsOn.map((d) => d.sourceID);
23220
- }
23221
- function* iterGraphSources(graph, sources) {
23222
- const seen = new Set;
23223
- function* visit(node) {
23224
- if (seen.has(node.sourceID))
23225
- return;
23226
- for (const dep of node.dependsOn) {
23227
- yield* visit(dep);
23218
+ function sourceBuildOf(build, unbuildable) {
23219
+ const order = [];
23220
+ const dependsOn = {};
23221
+ for (const target of build.targets) {
23222
+ const reads = target.dependsOn.flatMap((t) => t.sources.map((s) => s.sourceID));
23223
+ for (const source of target.sources) {
23224
+ order.push(source.sourceID);
23225
+ dependsOn[source.sourceID] = reads;
23228
23226
  }
23229
- seen.add(node.sourceID);
23230
- const source = sources[node.sourceID];
23227
+ }
23228
+ for (const { source } of unbuildable) {
23229
+ if (source.connectionName !== build.connectionName)
23230
+ continue;
23231
+ order.push(source.sourceID);
23232
+ dependsOn[source.sourceID] = [];
23233
+ }
23234
+ return { connectionName: build.connectionName, order, dependsOn };
23235
+ }
23236
+ function sourceBuildsOf(targets) {
23237
+ const builds = targets.connections.map((c) => sourceBuildOf(c, targets.unbuildable));
23238
+ const planned = new Set(builds.map((b) => b.connectionName));
23239
+ for (const { source } of targets.unbuildable) {
23240
+ if (planned.has(source.connectionName))
23241
+ continue;
23242
+ planned.add(source.connectionName);
23243
+ builds.push(sourceBuildOf({ connectionName: source.connectionName, targets: [] }, targets.unbuildable));
23244
+ }
23245
+ return builds;
23246
+ }
23247
+ function plannedSources(targets) {
23248
+ return [
23249
+ ...targets.connections.flatMap((c) => c.targets.flatMap((t) => t.sources)),
23250
+ ...targets.unbuildable.map((u) => u.source)
23251
+ ];
23252
+ }
23253
+ function* sourcesInBuildOrder(build, sources) {
23254
+ for (const sourceID of build.order) {
23255
+ const source = sources[sourceID];
23231
23256
  if (source)
23232
23257
  yield source;
23233
23258
  }
23234
- for (const level of graph.nodes) {
23235
- for (const node of level) {
23236
- yield* visit(node);
23237
- }
23238
- }
23239
23259
  }
23240
23260
  function computeSourceEntityId(source, connectionDigests) {
23241
23261
  return source.makeBuildId(connectionDigests[source.connectionName], addressPartitionLayout(source, source.getSQL()));
@@ -23324,7 +23344,7 @@ async function classifyPersistSourceGate(persistSource, modelDef, materializer,
23324
23344
  }
23325
23345
  function emptyBuildPlanParts() {
23326
23346
  return {
23327
- graphs: [],
23347
+ builds: [],
23328
23348
  sources: {},
23329
23349
  sourceModelPaths: {},
23330
23350
  droppedPersistSources: [],
@@ -23333,7 +23353,7 @@ function emptyBuildPlanParts() {
23333
23353
  };
23334
23354
  }
23335
23355
  function mergeBuildPlanParts(into, from) {
23336
- into.graphs.push(...from.graphs);
23356
+ into.builds.push(...from.builds);
23337
23357
  Object.assign(into.sources, from.sources);
23338
23358
  Object.assign(into.sourceModelPaths, from.sourceModelPaths);
23339
23359
  into.droppedPersistSources.push(...from.droppedPersistSources);
@@ -23341,7 +23361,14 @@ function mergeBuildPlanParts(into, from) {
23341
23361
  Object.assign(into.sourceGateOutcomes, from.sourceGateOutcomes);
23342
23362
  }
23343
23363
  async function collectModelBuildPlan(parts, args) {
23344
- const { modelPath, packagePath, materializer, malloyModel, getRuntime } = args;
23364
+ const {
23365
+ modelPath,
23366
+ packagePath,
23367
+ materializer,
23368
+ malloyModel,
23369
+ runtime,
23370
+ getRuntime
23371
+ } = args;
23345
23372
  const synthesized = args.synthesized !== undefined ? args.synthesized : await tryCompileSynthesizedPreaggregation({
23346
23373
  packagePath,
23347
23374
  modelPath,
@@ -23349,14 +23376,14 @@ async function collectModelBuildPlan(parts, args) {
23349
23376
  contents: malloyModel._modelDef?.contents ?? {}
23350
23377
  });
23351
23378
  if (synthesized) {
23352
- const rollupPlan = synthesized.model.getBuildPlan();
23353
- const rollupNames = new Set(synthesized.plans.map((p) => p.rollupSourceName));
23354
- parts.graphs.push(...rollupPlan.graphs);
23379
+ const rollupTargets = await synthesized.runtime.getBuildTargets(synthesized.model);
23380
+ parts.builds.push(...sourceBuildsOf(rollupTargets));
23355
23381
  const planByName = new Map(synthesized.plans.map((p) => [p.rollupSourceName, p]));
23356
23382
  let rollupClassificationCtx;
23357
- for (const [sourceID, source] of Object.entries(rollupPlan.sources)) {
23358
- if (!rollupNames.has(source.name))
23383
+ for (const source of plannedSources(rollupTargets)) {
23384
+ if (!planByName.has(source.name))
23359
23385
  continue;
23386
+ const sourceID = source.sourceID;
23360
23387
  parts.sources[sourceID] = source;
23361
23388
  parts.sourceModelPaths[sourceID] = modelPath;
23362
23389
  const rollup = planByName.get(source.name);
@@ -23375,23 +23402,25 @@ async function collectModelBuildPlan(parts, args) {
23375
23402
  const modelTag = malloyModel.modelAnnotations.parseAsTag("!").tag;
23376
23403
  if (!modelTag.has("experimental", "persistence"))
23377
23404
  return;
23378
- const buildPlan = malloyModel.getBuildPlan();
23379
- for (const msg of buildPlan.tagParseLog) {
23405
+ const targets = await runtime.getBuildTargets(malloyModel);
23406
+ for (const msg of targets.tagParseLog) {
23380
23407
  logger.warn("Persist annotation issue", {
23381
23408
  modelPath,
23382
23409
  message: msg.message,
23383
23410
  severity: msg.severity
23384
23411
  });
23385
23412
  }
23386
- const recognizedNames = new Set(Object.values(buildPlan.sources).map((s) => s.name));
23413
+ const planned = plannedSources(targets);
23414
+ const recognizedNames = new Set(planned.map((s) => s.name));
23387
23415
  for (const name of detectDroppedPersistSources(malloyModel, recognizedNames)) {
23388
23416
  parts.droppedPersistSources.push({ name, modelPath });
23389
23417
  }
23390
- if (buildPlan.graphs.length === 0)
23418
+ if (planned.length === 0)
23391
23419
  return;
23392
- parts.graphs.push(...buildPlan.graphs);
23420
+ parts.builds.push(...sourceBuildsOf(targets));
23393
23421
  let classificationCtx;
23394
- for (const [sourceID, source] of Object.entries(buildPlan.sources)) {
23422
+ for (const source of planned) {
23423
+ const sourceID = source.sourceID;
23395
23424
  parts.sources[sourceID] = source;
23396
23425
  parts.sourceModelPaths[sourceID] = modelPath;
23397
23426
  if (!classificationCtx) {
@@ -23404,28 +23433,30 @@ async function collectModelBuildPlan(parts, args) {
23404
23433
  parts.sourceGateOutcomes[sourceID] = await classifyPersistSourceGate(source, classificationCtx.modelDef, materializer, classificationCtx.deps, modelPath);
23405
23434
  }
23406
23435
  }
23407
- async function resolveConnectionDigests(connections, graphs, onSkipped) {
23436
+ async function resolveConnectionDigests(connections, builds, onSkipped) {
23408
23437
  const connectionDigests = {};
23409
- for (const graph of graphs) {
23410
- const conn = connections.get(graph.connectionName);
23438
+ for (const build of builds) {
23439
+ const conn = connections.get(build.connectionName);
23411
23440
  if (!conn) {
23412
- onSkipped(graph.connectionName);
23441
+ onSkipped(build.connectionName);
23413
23442
  continue;
23414
23443
  }
23415
- if (!connectionDigests[graph.connectionName]) {
23416
- connectionDigests[graph.connectionName] = await conn.getDigest();
23444
+ if (!connectionDigests[build.connectionName]) {
23445
+ connectionDigests[build.connectionName] = await conn.getDigest();
23417
23446
  }
23418
23447
  }
23419
23448
  return connectionDigests;
23420
23449
  }
23421
- function deriveBuildPlan(graphs, sources, connectionDigests, sourceNames, sourceModelPaths, packageMaterialization, options) {
23450
+ function deriveBuildPlan(builds, sources, connectionDigests, sourceNames, sourceModelPaths, packageMaterialization, options) {
23422
23451
  const include = sourceNames ? new Set(sourceNames) : null;
23423
- const wireGraphs = graphs.map((graph) => ({
23424
- connectionName: graph.connectionName,
23425
- nodes: graph.nodes.map((level) => level.map((node) => ({
23426
- sourceID: node.sourceID,
23427
- dependsOn: flattenDependsOn(node)
23428
- })))
23452
+ const wireGraphs = builds.map((build) => ({
23453
+ connectionName: build.connectionName,
23454
+ nodes: [
23455
+ build.order.map((sourceID) => ({
23456
+ sourceID,
23457
+ dependsOn: build.dependsOn[sourceID] ?? []
23458
+ }))
23459
+ ]
23429
23460
  }));
23430
23461
  const wireSources = {};
23431
23462
  const refusedSources = {};
@@ -23514,7 +23545,7 @@ function deriveBuildPlan(graphs, sources, connectionDigests, sourceNames, source
23514
23545
  function deriveBuildPlanOutcome(compiled, materializationConfig) {
23515
23546
  const droppedPersistSources = compiled.droppedPersistSources ?? [];
23516
23547
  const incrementalDeclarations = collectIncrementalDeclarations(compiled.sources);
23517
- const plan = compiled.graphs.length === 0 ? null : deriveBuildPlan(compiled.graphs, compiled.sources, compiled.connectionDigests, undefined, compiled.sourceModelPaths, materializationConfig, {
23548
+ const plan = compiled.builds.length === 0 ? null : deriveBuildPlan(compiled.builds, compiled.sources, compiled.connectionDigests, undefined, compiled.sourceModelPaths, materializationConfig, {
23518
23549
  preaggregatePlans: compiled.preaggregatePlans,
23519
23550
  sourceGateOutcomes: compiled.sourceGateOutcomes
23520
23551
  });
@@ -24474,6 +24505,7 @@ async function compileMalloyModel(job, malloyConfig, modelPath, load) {
24474
24505
  modelPath,
24475
24506
  materializer: mm,
24476
24507
  model: compiled,
24508
+ runtime,
24477
24509
  importBaseURL,
24478
24510
  queries,
24479
24511
  sources
@@ -24559,6 +24591,7 @@ async function prepareForMainThread(job, malloyConfig, load, model) {
24559
24591
  packagePath: job.packagePath,
24560
24592
  materializer,
24561
24593
  malloyModel: model.model,
24594
+ runtime: model.runtime,
24562
24595
  getRuntime: companionRuntime,
24563
24596
  synthesized: companion
24564
24597
  });
@@ -24798,8 +24831,8 @@ async function finishWorkerBuildPlan(malloyConfig, modelPaths, load, materializa
24798
24831
  }
24799
24832
  const connections = await resolvePackageConnections({
24800
24833
  getMalloyConnection: (name) => malloyConfig.connections.lookupConnection(name)
24801
- }, parts.graphs.map((g) => g.connectionName));
24802
- const connectionDigests = await resolveConnectionDigests(connections, parts.graphs, (name) => digestSkipped.push(name));
24834
+ }, parts.builds.map((b) => b.connectionName));
24835
+ const connectionDigests = await resolveConnectionDigests(connections, parts.builds, (name) => digestSkipped.push(name));
24803
24836
  const outcome = deriveBuildPlanOutcome({ ...parts, connectionDigests }, materializationConfig);
24804
24837
  return {
24805
24838
  ok: true,
package/dist/server.mjs CHANGED
@@ -164832,7 +164832,7 @@ var init_telemetry = __esm(() => {
164832
164832
  });
164833
164833
 
164834
164834
  // package.json
164835
- var version = "0.10.3";
164835
+ var version = "0.10.4";
164836
164836
  var init_package = () => {};
164837
164837
 
164838
164838
  // src/version.ts
@@ -348115,7 +348115,7 @@ async function compileSynthesizedPreaggregation(args) {
348115
348115
  const { runtime, importBaseURL } = await args.getRuntime(new Map([[synthesizedURL.href, text]]));
348116
348116
  const materializer = runtime.loadModel(synthesizedURL, { importBaseURL });
348117
348117
  const model = await materializer.getModel();
348118
- return { plans, model, materializer, text };
348118
+ return { plans, model, materializer, runtime, text };
348119
348119
  }
348120
348120
  async function tryCompileSynthesizedPreaggregation(args) {
348121
348121
  try {
@@ -348302,27 +348302,47 @@ function composeDeclaredQueryMetadata(layers) {
348302
348302
  const resolved = Object.assign({}, ...ordered);
348303
348303
  return Object.keys(resolved).length > 0 ? resolved : null;
348304
348304
  }
348305
- function flattenDependsOn(node) {
348306
- return node.dependsOn.map((d) => d.sourceID);
348307
- }
348308
- function* iterGraphSources(graph, sources) {
348309
- const seen = new Set;
348310
- function* visit(node) {
348311
- if (seen.has(node.sourceID))
348312
- return;
348313
- for (const dep of node.dependsOn) {
348314
- yield* visit(dep);
348305
+ function sourceBuildOf(build, unbuildable) {
348306
+ const order = [];
348307
+ const dependsOn = {};
348308
+ for (const target of build.targets) {
348309
+ const reads = target.dependsOn.flatMap((t2) => t2.sources.map((s2) => s2.sourceID));
348310
+ for (const source of target.sources) {
348311
+ order.push(source.sourceID);
348312
+ dependsOn[source.sourceID] = reads;
348315
348313
  }
348316
- seen.add(node.sourceID);
348317
- const source = sources[node.sourceID];
348314
+ }
348315
+ for (const { source } of unbuildable) {
348316
+ if (source.connectionName !== build.connectionName)
348317
+ continue;
348318
+ order.push(source.sourceID);
348319
+ dependsOn[source.sourceID] = [];
348320
+ }
348321
+ return { connectionName: build.connectionName, order, dependsOn };
348322
+ }
348323
+ function sourceBuildsOf(targets) {
348324
+ const builds = targets.connections.map((c) => sourceBuildOf(c, targets.unbuildable));
348325
+ const planned = new Set(builds.map((b) => b.connectionName));
348326
+ for (const { source } of targets.unbuildable) {
348327
+ if (planned.has(source.connectionName))
348328
+ continue;
348329
+ planned.add(source.connectionName);
348330
+ builds.push(sourceBuildOf({ connectionName: source.connectionName, targets: [] }, targets.unbuildable));
348331
+ }
348332
+ return builds;
348333
+ }
348334
+ function plannedSources(targets) {
348335
+ return [
348336
+ ...targets.connections.flatMap((c) => c.targets.flatMap((t2) => t2.sources)),
348337
+ ...targets.unbuildable.map((u) => u.source)
348338
+ ];
348339
+ }
348340
+ function* sourcesInBuildOrder(build, sources) {
348341
+ for (const sourceID of build.order) {
348342
+ const source = sources[sourceID];
348318
348343
  if (source)
348319
348344
  yield source;
348320
348345
  }
348321
- for (const level of graph.nodes) {
348322
- for (const node of level) {
348323
- yield* visit(node);
348324
- }
348325
- }
348326
348346
  }
348327
348347
  function computeSourceEntityId(source, connectionDigests) {
348328
348348
  return source.makeBuildId(connectionDigests[source.connectionName], addressPartitionLayout(source, source.getSQL()));
@@ -348411,7 +348431,7 @@ async function classifyPersistSourceGate(persistSource, modelDef, materializer,
348411
348431
  }
348412
348432
  function emptyBuildPlanParts() {
348413
348433
  return {
348414
- graphs: [],
348434
+ builds: [],
348415
348435
  sources: {},
348416
348436
  sourceModelPaths: {},
348417
348437
  droppedPersistSources: [],
@@ -348420,7 +348440,7 @@ function emptyBuildPlanParts() {
348420
348440
  };
348421
348441
  }
348422
348442
  function mergeBuildPlanParts(into, from) {
348423
- into.graphs.push(...from.graphs);
348443
+ into.builds.push(...from.builds);
348424
348444
  Object.assign(into.sources, from.sources);
348425
348445
  Object.assign(into.sourceModelPaths, from.sourceModelPaths);
348426
348446
  into.droppedPersistSources.push(...from.droppedPersistSources);
@@ -348428,7 +348448,14 @@ function mergeBuildPlanParts(into, from) {
348428
348448
  Object.assign(into.sourceGateOutcomes, from.sourceGateOutcomes);
348429
348449
  }
348430
348450
  async function collectModelBuildPlan(parts, args) {
348431
- const { modelPath, packagePath, materializer, malloyModel, getRuntime } = args;
348451
+ const {
348452
+ modelPath,
348453
+ packagePath,
348454
+ materializer,
348455
+ malloyModel,
348456
+ runtime,
348457
+ getRuntime
348458
+ } = args;
348432
348459
  const synthesized = args.synthesized !== undefined ? args.synthesized : await tryCompileSynthesizedPreaggregation({
348433
348460
  packagePath,
348434
348461
  modelPath,
@@ -348436,14 +348463,14 @@ async function collectModelBuildPlan(parts, args) {
348436
348463
  contents: malloyModel._modelDef?.contents ?? {}
348437
348464
  });
348438
348465
  if (synthesized) {
348439
- const rollupPlan = synthesized.model.getBuildPlan();
348440
- const rollupNames = new Set(synthesized.plans.map((p) => p.rollupSourceName));
348441
- parts.graphs.push(...rollupPlan.graphs);
348466
+ const rollupTargets = await synthesized.runtime.getBuildTargets(synthesized.model);
348467
+ parts.builds.push(...sourceBuildsOf(rollupTargets));
348442
348468
  const planByName = new Map(synthesized.plans.map((p) => [p.rollupSourceName, p]));
348443
348469
  let rollupClassificationCtx;
348444
- for (const [sourceID, source] of Object.entries(rollupPlan.sources)) {
348445
- if (!rollupNames.has(source.name))
348470
+ for (const source of plannedSources(rollupTargets)) {
348471
+ if (!planByName.has(source.name))
348446
348472
  continue;
348473
+ const sourceID = source.sourceID;
348447
348474
  parts.sources[sourceID] = source;
348448
348475
  parts.sourceModelPaths[sourceID] = modelPath;
348449
348476
  const rollup = planByName.get(source.name);
@@ -348462,23 +348489,25 @@ async function collectModelBuildPlan(parts, args) {
348462
348489
  const modelTag = malloyModel.modelAnnotations.parseAsTag("!").tag;
348463
348490
  if (!modelTag.has("experimental", "persistence"))
348464
348491
  return;
348465
- const buildPlan = malloyModel.getBuildPlan();
348466
- for (const msg of buildPlan.tagParseLog) {
348492
+ const targets = await runtime.getBuildTargets(malloyModel);
348493
+ for (const msg of targets.tagParseLog) {
348467
348494
  logger.warn("Persist annotation issue", {
348468
348495
  modelPath,
348469
348496
  message: msg.message,
348470
348497
  severity: msg.severity
348471
348498
  });
348472
348499
  }
348473
- const recognizedNames = new Set(Object.values(buildPlan.sources).map((s2) => s2.name));
348500
+ const planned = plannedSources(targets);
348501
+ const recognizedNames = new Set(planned.map((s2) => s2.name));
348474
348502
  for (const name of detectDroppedPersistSources(malloyModel, recognizedNames)) {
348475
348503
  parts.droppedPersistSources.push({ name, modelPath });
348476
348504
  }
348477
- if (buildPlan.graphs.length === 0)
348505
+ if (planned.length === 0)
348478
348506
  return;
348479
- parts.graphs.push(...buildPlan.graphs);
348507
+ parts.builds.push(...sourceBuildsOf(targets));
348480
348508
  let classificationCtx;
348481
- for (const [sourceID, source] of Object.entries(buildPlan.sources)) {
348509
+ for (const source of planned) {
348510
+ const sourceID = source.sourceID;
348482
348511
  parts.sources[sourceID] = source;
348483
348512
  parts.sourceModelPaths[sourceID] = modelPath;
348484
348513
  if (!classificationCtx) {
@@ -348491,28 +348520,30 @@ async function collectModelBuildPlan(parts, args) {
348491
348520
  parts.sourceGateOutcomes[sourceID] = await classifyPersistSourceGate(source, classificationCtx.modelDef, materializer, classificationCtx.deps, modelPath);
348492
348521
  }
348493
348522
  }
348494
- async function resolveConnectionDigests(connections, graphs, onSkipped) {
348523
+ async function resolveConnectionDigests(connections, builds, onSkipped) {
348495
348524
  const connectionDigests = {};
348496
- for (const graph of graphs) {
348497
- const conn = connections.get(graph.connectionName);
348525
+ for (const build of builds) {
348526
+ const conn = connections.get(build.connectionName);
348498
348527
  if (!conn) {
348499
- onSkipped(graph.connectionName);
348528
+ onSkipped(build.connectionName);
348500
348529
  continue;
348501
348530
  }
348502
- if (!connectionDigests[graph.connectionName]) {
348503
- connectionDigests[graph.connectionName] = await conn.getDigest();
348531
+ if (!connectionDigests[build.connectionName]) {
348532
+ connectionDigests[build.connectionName] = await conn.getDigest();
348504
348533
  }
348505
348534
  }
348506
348535
  return connectionDigests;
348507
348536
  }
348508
- function deriveBuildPlan(graphs, sources, connectionDigests, sourceNames, sourceModelPaths, packageMaterialization, options) {
348537
+ function deriveBuildPlan(builds, sources, connectionDigests, sourceNames, sourceModelPaths, packageMaterialization, options) {
348509
348538
  const include = sourceNames ? new Set(sourceNames) : null;
348510
- const wireGraphs = graphs.map((graph) => ({
348511
- connectionName: graph.connectionName,
348512
- nodes: graph.nodes.map((level) => level.map((node) => ({
348513
- sourceID: node.sourceID,
348514
- dependsOn: flattenDependsOn(node)
348515
- })))
348539
+ const wireGraphs = builds.map((build) => ({
348540
+ connectionName: build.connectionName,
348541
+ nodes: [
348542
+ build.order.map((sourceID) => ({
348543
+ sourceID,
348544
+ dependsOn: build.dependsOn[sourceID] ?? []
348545
+ }))
348546
+ ]
348516
348547
  }));
348517
348548
  const wireSources = {};
348518
348549
  const refusedSources = {};
@@ -348601,7 +348632,7 @@ function deriveBuildPlan(graphs, sources, connectionDigests, sourceNames, source
348601
348632
  function deriveBuildPlanOutcome(compiled, materializationConfig) {
348602
348633
  const droppedPersistSources = compiled.droppedPersistSources ?? [];
348603
348634
  const incrementalDeclarations = collectIncrementalDeclarations(compiled.sources);
348604
- const plan = compiled.graphs.length === 0 ? null : deriveBuildPlan(compiled.graphs, compiled.sources, compiled.connectionDigests, undefined, compiled.sourceModelPaths, materializationConfig, {
348635
+ const plan = compiled.builds.length === 0 ? null : deriveBuildPlan(compiled.builds, compiled.sources, compiled.connectionDigests, undefined, compiled.sourceModelPaths, materializationConfig, {
348605
348636
  preaggregatePlans: compiled.preaggregatePlans,
348606
348637
  sourceGateOutcomes: compiled.sourceGateOutcomes
348607
348638
  });
@@ -350136,14 +350167,14 @@ async function buildDownstreamIntoStorage(params) {
350136
350167
  message: `Chained build model did not compile over the rebound parents: ${errMessage(err)}`
350137
350168
  });
350138
350169
  }
350139
- const plan = model.getBuildPlan();
350140
- let downstream;
350141
- for (const ps of Object.values(plan.sources)) {
350142
- if (ps.name === downstreamName) {
350143
- downstream = ps;
350144
- break;
350145
- }
350170
+ const targets = await runtime.getBuildTargets(model, { virtualMap });
350171
+ const unbuildable = targets.unbuildable.find((u) => u.source.name === downstreamName);
350172
+ if (unbuildable) {
350173
+ throw new MaterializationEligibilityError({
350174
+ message: `Chained build model could not plan '${downstreamName}' over ` + `the rebound parents: ${unbuildable.problem.message}`
350175
+ });
350146
350176
  }
350177
+ const downstream = plannedSources(targets).find((ps) => ps.name === downstreamName);
350147
350178
  if (!downstream) {
350148
350179
  throw new MaterializationEligibilityError({
350149
350180
  message: `Chained build model did not yield a persist source named ` + `'${downstreamName}' (the downstream references something the ` + `rebound parents don't provide).`
@@ -359860,6 +359891,98 @@ var require_recursive_readdir = __commonJS((exports, module) => {
359860
359891
  module.exports = readdir3;
359861
359892
  });
359862
359893
 
359894
+ // src/service/preaggregation_coverage.ts
359895
+ function rollupMeasuresByMember(plans) {
359896
+ return new Map(plans.map((p2) => [
359897
+ p2.rollupSourceName,
359898
+ new Set(p2.measures.map((m3) => m3.name))
359899
+ ]));
359900
+ }
359901
+ function rollupCoverageViolation(query, rollupMeasures) {
359902
+ const ir2 = query;
359903
+ const member = ir2?.compositeResolvedSourceDef?.as;
359904
+ if (!member)
359905
+ return;
359906
+ const merged = rollupMeasures.get(member);
359907
+ if (!merged)
359908
+ return;
359909
+ const memberFields = new Map((ir2?.compositeResolvedSourceDef?.fields ?? []).map((f5) => [
359910
+ f5.as ?? f5.name,
359911
+ f5
359912
+ ]));
359913
+ const followed = new Set;
359914
+ let violation;
359915
+ const visitRef = (path16) => {
359916
+ if (!path16)
359917
+ return;
359918
+ if (path16.length !== 1) {
359919
+ violation = `reference ${path16.join(".")} reaches past the rollup's own fields`;
359920
+ return;
359921
+ }
359922
+ const name = path16[0];
359923
+ if (merged.has(name) || followed.has(name))
359924
+ return;
359925
+ followed.add(name);
359926
+ visitExpr(memberFields.get(name)?.e);
359927
+ };
359928
+ const visitExpr = (e2) => {
359929
+ if (violation || !e2 || typeof e2 !== "object")
359930
+ return;
359931
+ if (Array.isArray(e2)) {
359932
+ for (const item of e2)
359933
+ visitExpr(item);
359934
+ return;
359935
+ }
359936
+ const n = e2;
359937
+ if (n.node === "aggregate") {
359938
+ violation = `aggregate ${String(n.function)}() is computed over the rollup's rows, not from a stored partial`;
359939
+ return;
359940
+ }
359941
+ if (n.node === "function_call" && n.overload?.returnType?.expressionType === "aggregate") {
359942
+ violation = `aggregate function ${String(n.name)}() is computed over the rollup's rows, not from a stored partial`;
359943
+ return;
359944
+ }
359945
+ if (n.node === "field") {
359946
+ visitRef(n.path);
359947
+ return;
359948
+ }
359949
+ for (const [key, value] of Object.entries(n)) {
359950
+ if (key === "overload" || key === "at")
359951
+ continue;
359952
+ visitExpr(value);
359953
+ }
359954
+ };
359955
+ const visitStage = (stage) => {
359956
+ if (violation)
359957
+ return;
359958
+ if (stage?.type === "project" || stage?.type === "index") {
359959
+ violation = `${stage.type === "project" ? "a select:" : "an index:"} stage over the rollup returns one row per grain group, not the source's rows`;
359960
+ return;
359961
+ }
359962
+ for (const field of stage?.queryFields ?? []) {
359963
+ if (field.type === "fieldref")
359964
+ visitRef(field.path);
359965
+ else if (field.type === "turtle")
359966
+ visitStage(field.pipeline?.[0]);
359967
+ else
359968
+ visitExpr(field.e);
359969
+ }
359970
+ for (const filter2 of stage?.filterList ?? [])
359971
+ visitExpr(filter2.e);
359972
+ };
359973
+ visitStage(ir2?.pipeline?.[0]);
359974
+ return violation ? `${member}: ${violation}` : undefined;
359975
+ }
359976
+ var RollupCoverageError;
359977
+ var init_preaggregation_coverage = __esm(() => {
359978
+ RollupCoverageError = class RollupCoverageError extends Error {
359979
+ constructor(message) {
359980
+ super(message);
359981
+ this.name = "RollupCoverageError";
359982
+ }
359983
+ };
359984
+ });
359985
+
359863
359986
  // src/service/render_tag_targets.ts
359864
359987
  function renderTagTargets(queries, sources) {
359865
359988
  const targets = [];
@@ -363064,6 +363187,11 @@ class StorageServeShape {
363064
363187
  await this.host.ensureSharedQueryModel(this.serveShapeCache.materializer);
363065
363188
  const runnable = this.serveShapeCache.materializer.loadRestrictedQuery(queryString);
363066
363189
  const probeSQL = await runnable.getSQL({ virtualMap, givens });
363190
+ if (groups.length > 0) {
363191
+ const uncovered = rollupCoverageViolation((await runnable.getPreparedQuery())._query, rollupMeasuresByMember(this.host.rollupPlans()));
363192
+ if (uncovered)
363193
+ throw new RollupCoverageError(uncovered);
363194
+ }
363067
363195
  const rollupPaths = [
363068
363196
  ...buildVirtualMap(groups.flatMap((g2) => g2.members))
363069
363197
  ].flatMap(([, byHandle]) => [...byHandle.values()]);
@@ -363264,6 +363392,7 @@ var init_model_serve_shape = __esm(() => {
363264
363392
  init_materialization_metrics();
363265
363393
  init_connection();
363266
363394
  init_materialization_serve_transform();
363395
+ init_preaggregation_coverage();
363267
363396
  init_preaggregation_serve_bindings();
363268
363397
  staleBindingsWithheld = lazyCounter("publisher_storage_bindings_withheld_total", "Storage serve bindings withheld from a routed query because the freshness gate found them stale; each one is read live instead. Until now staleness was visible only as a later live read.");
363269
363398
  });
@@ -363306,6 +363435,7 @@ var init_model = __esm(() => {
363306
363435
  init_config();
363307
363436
  init_preaggregation_synthesis();
363308
363437
  init_preaggregation_compile();
363438
+ init_preaggregation_coverage();
363309
363439
  init_render_tag_targets();
363310
363440
  init_model_info();
363311
363441
  init_logger();
@@ -365487,13 +365617,17 @@ run: ${sourceName ? `${quoteMalloyIdentifier2(sourceName)} -> ` : ""}${quoteMall
365487
365617
  });
365488
365618
  } catch (shapeErr) {
365489
365619
  recordStorageServeRouting("live_fallback");
365490
- const { shapeSources, staleSources } = serveShapeDiagnostics(this.storageServe.serveBindings, this.storageServe.freshServeBindings(Date.now()));
365491
- logger.info("storage serve-shape ineligible for this query; serving live", {
365492
- modelPath: this.modelPath,
365493
- error: shapeErr instanceof Error ? shapeErr.message : String(shapeErr),
365494
- shapeSources,
365495
- staleSources
365496
- });
365620
+ if (shapeErr instanceof RollupCoverageError) {
365621
+ logger.debug("storage rollup cannot answer this query; serving live", { modelPath: this.modelPath, error: shapeErr.message });
365622
+ } else {
365623
+ const { shapeSources, staleSources } = serveShapeDiagnostics(this.storageServe.serveBindings, this.storageServe.freshServeBindings(Date.now()));
365624
+ logger.info("storage serve-shape ineligible for this query; serving live", {
365625
+ modelPath: this.modelPath,
365626
+ error: shapeErr instanceof Error ? shapeErr.message : String(shapeErr),
365627
+ shapeSources,
365628
+ staleSources
365629
+ });
365630
+ }
365497
365631
  }
365498
365632
  }
365499
365633
  const everyRollupIsStorageBound = this.preaggregateRollupPlans.length > 0 && this.preaggregateRollupPlans.every((plan) => !!plan.storage);
@@ -365504,10 +365638,13 @@ run: ${sourceName ? `${quoteMalloyIdentifier2(sourceName)} -> ` : ""}${quoteMall
365504
365638
  givens: querySurfaceGivens,
365505
365639
  buildManifest
365506
365640
  });
365641
+ const uncovered = rollupCoverageViolation((await candidate.getPreparedQuery())._query, rollupMeasuresByMember(this.preaggregateRollupPlans));
365642
+ if (uncovered)
365643
+ throw new RollupCoverageError(uncovered);
365507
365644
  runnable = candidate;
365508
365645
  preaggRouted = true;
365509
365646
  } catch (preaggErr) {
365510
- logger.debug("query does not compile against the pre-aggregation model; serving live", {
365647
+ logger.debug("query cannot be served from the pre-aggregation model; serving live", {
365511
365648
  modelPath: this.modelPath,
365512
365649
  error: preaggErr instanceof Error ? preaggErr.message : String(preaggErr)
365513
365650
  });
@@ -366140,18 +366277,19 @@ async function compilePackageBuildPlan(pkg, signal) {
366140
366277
  packagePath: pkg.getPackagePath(),
366141
366278
  materializer,
366142
366279
  malloyModel,
366280
+ runtime,
366143
366281
  getRuntime: (overlay) => Model.getModelRuntime(pkg.getPackagePath(), modelPath, pkg.getMalloyConfig(), { overlay })
366144
366282
  });
366145
366283
  }
366146
- const connections = await resolvePackageConnections(pkg, parts.graphs.map((g2) => g2.connectionName));
366147
- const connectionDigests = await resolveConnectionDigests(connections, parts.graphs, (connectionName) => {
366284
+ const connections = await resolvePackageConnections(pkg, parts.builds.map((b3) => b3.connectionName));
366285
+ const connectionDigests = await resolveConnectionDigests(connections, parts.builds, (connectionName) => {
366148
366286
  recordConnectionDigestSkipped();
366149
366287
  logger.warn("Skipping connection digest; connection did not resolve", {
366150
366288
  connectionName
366151
366289
  });
366152
366290
  });
366153
366291
  return {
366154
- graphs: parts.graphs,
366292
+ builds: parts.builds,
366155
366293
  sources: parts.sources,
366156
366294
  connectionDigests,
366157
366295
  connections,
@@ -374591,11 +374729,11 @@ function resolveInstruction(index, persistSource, connectionDigests) {
374591
374729
  return;
374592
374730
  return { instruction, orchestrated: false, sourceEntityId };
374593
374731
  }
374594
- function assertNoTableCollisions(graphs, sources, connectionDigests, index) {
374732
+ function assertNoTableCollisions(builds, sources, connectionDigests, index) {
374595
374733
  const claimedBy = new Map;
374596
374734
  const collisions = [];
374597
- for (const graph of graphs) {
374598
- for (const persistSource of iterGraphSources(graph, sources)) {
374735
+ for (const build of builds) {
374736
+ for (const persistSource of sourcesInBuildOrder(build, sources)) {
374599
374737
  let address;
374600
374738
  try {
374601
374739
  address = computeSourceEntityId(persistSource, connectionDigests);
@@ -374605,7 +374743,7 @@ function assertNoTableCollisions(graphs, sources, connectionDigests, index) {
374605
374743
  const instruction = index.bySourceID.get(persistSource.sourceID) ?? index.bySourceEntityId.get(address);
374606
374744
  if (!instruction)
374607
374745
  continue;
374608
- const target = physicalTargetKey(instruction, graph.connectionName);
374746
+ const target = physicalTargetKey(instruction, build.connectionName);
374609
374747
  const claim = claimedBy.get(target);
374610
374748
  if (!claim) {
374611
374749
  claimedBy.set(target, {
@@ -374896,8 +375034,8 @@ class MaterializationService {
374896
375034
  const refusals = [];
374897
375035
  const cleared = new Map;
374898
375036
  const seen = new Set;
374899
- for (const graph of compiled.graphs) {
374900
- for (const persistSource of iterGraphSources(graph, compiled.sources)) {
375037
+ for (const build of compiled.builds) {
375038
+ for (const persistSource of sourcesInBuildOrder(build, compiled.sources)) {
374901
375039
  if (include && !include.has(persistSource.name))
374902
375040
  continue;
374903
375041
  if (getPersistStorageMode() === "off" && declaredStorage(persistSource)) {
@@ -375153,7 +375291,7 @@ class MaterializationService {
375153
375291
  return incrementalRunContext(compiled, run, this.repository);
375154
375292
  }
375155
375293
  async executeInstructedBuild(compiled, environment, instructions, seedEntries, signal, strict = false, owner, buildMetadata, incremental, priorRefusals = [], clearedTiers) {
375156
- const { graphs, sources, connectionDigests, connections } = compiled;
375294
+ const { builds, sources, connectionDigests, connections } = compiled;
375157
375295
  const index = indexInstructions(instructions);
375158
375296
  const run = {
375159
375297
  compiled,
@@ -375173,17 +375311,17 @@ class MaterializationService {
375173
375311
  builtSources: [],
375174
375312
  writtenTargets: new Map
375175
375313
  };
375176
- assertNoTableCollisions(graphs, sources, connectionDigests, index);
375314
+ assertNoTableCollisions(builds, sources, connectionDigests, index);
375177
375315
  try {
375178
- for (const graph of graphs) {
375179
- const connection = connections.get(graph.connectionName);
375316
+ for (const build of builds) {
375317
+ const connection = connections.get(build.connectionName);
375180
375318
  if (!connection) {
375181
- throw new BadRequestError(`Connection '${graph.connectionName}' not found`);
375319
+ throw new BadRequestError(`Connection '${build.connectionName}' not found`);
375182
375320
  }
375183
- for (const persistSource of iterGraphSources(graph, sources)) {
375321
+ for (const persistSource of sourcesInBuildOrder(build, sources)) {
375184
375322
  if (signal.aborted)
375185
375323
  throw new Error("Build cancelled");
375186
- await this.buildInstructedSource(run, persistSource, graph.connectionName, connection);
375324
+ await this.buildInstructedSource(run, persistSource, build.connectionName, connection);
375187
375325
  }
375188
375326
  }
375189
375327
  if (run.builtSources.length === 0 && run.failedReasons.length > 0) {
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "snowflake"
20
20
  ],
21
21
  "homepage": "https://docs.malloydata.dev/documentation/user_guides/publishing/mcp_agents",
22
- "version": "0.10.3",
22
+ "version": "0.10.4",
23
23
  "main": "dist/server.mjs",
24
24
  "bin": {
25
25
  "malloy-publisher": "dist/server.mjs"