@malloy-publisher/server 0.9.1 → 0.9.2

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 (31) hide show
  1. package/dist/app/api-doc.yaml +127 -53
  2. package/dist/app/assets/EnvironmentPage-B-t_QP32.js +1 -0
  3. package/dist/app/assets/{HomePage-D3MvQaZn.js → HomePage-Cpu2zMy-.js} +1 -1
  4. package/dist/app/assets/{LightMode-e1lNBfpL.js → LightMode-DOBl6awB.js} +1 -1
  5. package/dist/app/assets/MainPage-C7ySrclP.js +1 -0
  6. package/dist/app/assets/ModelPage-D8r_J-N9.js +2 -0
  7. package/dist/app/assets/PackagePage-CcVFBhnT.js +1 -0
  8. package/dist/app/assets/RenderedResult-CBLHr32C.es-DRr9Gmsz.js +141 -0
  9. package/dist/app/assets/{RouteError-BQnzPJvb.js → RouteError-mgOkv7XK.js} +1 -1
  10. package/dist/app/assets/ThemeEditorPage-CIPDs2w9.js +1 -0
  11. package/dist/app/assets/ToggleButtonGroup-CEZThrzh.js +1 -0
  12. package/dist/app/assets/{index-CCNqF0-G.js → index-Bg-M8sZz.js} +1 -1
  13. package/dist/app/assets/index-CGdwupPP.js +457 -0
  14. package/dist/app/assets/{index-CKmZ4HG7.js → index-CeLTZAFW.js} +116 -116
  15. package/dist/app/assets/{index-CdExa3Pk.js → index-DoF8ihra.js} +1 -1
  16. package/dist/app/assets/{index-ufFZR_K-.js → index-IC0jPCkR.js} +1 -1
  17. package/dist/app/assets/index.es-6eARPKZf.js +198 -0
  18. package/dist/app/index.html +1 -1
  19. package/dist/package_load_worker.mjs +436 -56
  20. package/dist/server.mjs +9537 -9076
  21. package/package.json +1 -1
  22. package/dist/app/assets/EnvironmentPage-UMS7Py4Q.js +0 -1
  23. package/dist/app/assets/MainPage-B0ozqtQO.js +0 -2
  24. package/dist/app/assets/ModelPage-lQj4Axqu.js +0 -2
  25. package/dist/app/assets/PackagePage-BT0YKNKU.js +0 -1
  26. package/dist/app/assets/RenderedResult-BRgIgxWh.es-DiE7kPNX.js +0 -136
  27. package/dist/app/assets/ThemeEditorPage-D6t7w9Js.js +0 -1
  28. package/dist/app/assets/ToggleButtonGroup-CJT2JKTC.js +0 -1
  29. package/dist/app/assets/index-TiWjmdoW.js +0 -450
  30. package/dist/app/assets/index.es-iUV0gCYk.js +0 -111
  31. package/dist/cpufeatures-1yrn0vtw.node +0 -0
@@ -13195,7 +13195,7 @@ function toQueryTextProblem(problem) {
13195
13195
  ...problem.at ? { at: { range: problem.at.range } } : {}
13196
13196
  };
13197
13197
  }
13198
- var GENERIC_INTERNAL_MESSAGE = "Internal server error.", GENERIC_UPSTREAM_MESSAGE = "Upstream connection error.", MAX_LOGGED_DETAIL_CHARS = 2000, FILESYSTEM_ACCESS_DESCRIPTIONS, NotImplementedError, BadRequestError, InvalidArgumentError, CompileRefusedError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, ConnectionNotFoundError, TableNotFoundError, ConnectionError, DestinationNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, PackageManifestError, MaterializationEligibilityError, PublisherConfigError, ServerConfigurationError, FrozenConfigError, AccessDeniedError, QueryCompileError, NotQueryableError, OffSurfaceError, MaterializationNotFoundError, MaterializationConflictError, WriteConflictError, WriteRolledBackError, InvalidStateTransitionError, ServiceUnavailableError, PayloadTooLargeError, ResponseUnserializableError, QueryTimeoutError;
13198
+ var GENERIC_INTERNAL_MESSAGE = "Internal server error.", GENERIC_UPSTREAM_MESSAGE = "Upstream connection error.", MAX_LOGGED_DETAIL_CHARS = 2000, FILESYSTEM_ACCESS_DESCRIPTIONS, NotImplementedError, BadRequestError, InvalidArgumentError, CompileRefusedError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, ConnectionNotFoundError, TableNotFoundError, ConnectionError, DestinationNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, PackageManifestError, MaterializationEligibilityError, PublisherConfigError, ServerConfigurationError, FrozenConfigError, AccessDeniedError, QueryCompileError, NotQueryableError, OffSurfaceError, MaterializationNotFoundError, MaterializationConflictError, WriteConflictError, WriteRolledBackError, WriteVerifyError, InvalidStateTransitionError, ServiceUnavailableError, PayloadTooLargeError, ResponseUnserializableError, QueryTimeoutError;
13199
13199
  var init_errors = __esm(() => {
13200
13200
  init_constants();
13201
13201
  init_logger();
@@ -13348,11 +13348,17 @@ var init_errors = __esm(() => {
13348
13348
  }
13349
13349
  };
13350
13350
  WriteRolledBackError = class WriteRolledBackError extends Error {
13351
- constructor(message) {
13352
- super(message);
13351
+ constructor(message, options) {
13352
+ super(message, options);
13353
13353
  this.name = "WriteRolledBackError";
13354
13354
  }
13355
13355
  };
13356
+ WriteVerifyError = class WriteVerifyError extends Error {
13357
+ constructor(message, options) {
13358
+ super(message, options);
13359
+ this.name = "WriteVerifyError";
13360
+ }
13361
+ };
13356
13362
  InvalidStateTransitionError = class InvalidStateTransitionError extends Error {
13357
13363
  constructor(message) {
13358
13364
  super(message);
@@ -13407,6 +13413,7 @@ var init_authorize_routes = __esm(() => {
13407
13413
 
13408
13414
  // src/service/authorize.ts
13409
13415
  import { payloadOf, routeOf } from "@malloydata/malloy";
13416
+ import { createRequire as createRequire2 } from "module";
13410
13417
  function noteRoute(text) {
13411
13418
  return routeOf({ value: text.trimStart() });
13412
13419
  }
@@ -13455,7 +13462,7 @@ function motlyAuthorizePayloadPattern(route) {
13455
13462
  return new RegExp(`^[ \\t]*[([{<][ \\t]*(?:${nearMissWordAlternation(route)})[ \\t]*[)\\]}>]`, "iu");
13456
13463
  }
13457
13464
  function malformedAuthorizeAttemptPattern(route) {
13458
- return new RegExp(`^##?\\|?[ \\t]*[([{<]?[ \\t]*(?:${nearMissWordAlternation(route)})`, "iu");
13465
+ return new RegExp(`^##?\\|?[ \\t]*(?:[([{<][ \\t]*)?(?:${nearMissWordAlternation(route)})`, "iu");
13459
13466
  }
13460
13467
  function authorizeNoteContent(text) {
13461
13468
  const route = canonicalAuthorizeRoute(noteRoute(text));
@@ -13466,12 +13473,144 @@ function authorizeNoteContent(text) {
13466
13473
  function authorizeAnnotationRoute(text) {
13467
13474
  return authorizeNoteContent(text)?.route;
13468
13475
  }
13469
- function hasCallerAuthorizeAnnotation(callerText) {
13470
- return AUTHORIZE_ANNOTATION_ANYWHERE.test(callerText);
13476
+ function loadMalloyParser() {
13477
+ try {
13478
+ const entry = createRequire2(import.meta.url).resolve("@malloydata/malloy");
13479
+ const loaded = createRequire2(entry)("./lang/run-malloy-parser");
13480
+ return typeof loaded.makeMalloyParser === "function" ? loaded.makeMalloyParser : null;
13481
+ } catch {
13482
+ return null;
13483
+ }
13484
+ }
13485
+ function malloyParserFactory() {
13486
+ if (makeMalloyParser === undefined)
13487
+ makeMalloyParser = parserLoader();
13488
+ return makeMalloyParser ?? undefined;
13489
+ }
13490
+ function lastCallerGuardRefusalKind() {
13491
+ return lastRefusalKind;
13492
+ }
13493
+ function proseRanges(text) {
13494
+ if (lastLexed.text !== text)
13495
+ lastLexed = { text, prose: lexProseRanges(text) };
13496
+ return lastLexed.prose;
13497
+ }
13498
+ function lexProseRanges(text) {
13499
+ const factory = malloyParserFactory();
13500
+ lexerMissing = !factory;
13501
+ if (!factory) {
13502
+ if (!warnedNoParser) {
13503
+ warnedNoParser = true;
13504
+ console.warn("Prose-aware caller-annotation guard disabled: @malloydata/malloy ./lang/run-malloy-parser could not be loaded; falling back to whole-text matching.");
13505
+ }
13506
+ return;
13507
+ }
13508
+ try {
13509
+ const counter = () => ({
13510
+ errors: 0,
13511
+ syntaxError() {
13512
+ this.errors++;
13513
+ }
13514
+ });
13515
+ const lexerErrors = counter();
13516
+ const parserErrors = counter();
13517
+ const { tokenStream, parser } = factory(text, {
13518
+ lexerErrorListener: lexerErrors,
13519
+ parserErrorListener: parserErrors
13520
+ });
13521
+ parser.malloyDocument();
13522
+ const vocabulary = tokenStream.tokenSource?.vocabulary;
13523
+ if (lexerErrors.errors > 0 || parserErrors.errors > 0 || typeof vocabulary?.getSymbolicName !== "function")
13524
+ return;
13525
+ const utf16 = [];
13526
+ for (let i = 0;i < text.length; ) {
13527
+ utf16.push(i);
13528
+ i += text.codePointAt(i) > 65535 ? 2 : 1;
13529
+ }
13530
+ utf16.push(text.length);
13531
+ const rangeOf = (token) => {
13532
+ const from = utf16[token.startIndex];
13533
+ const to = utf16[token.stopIndex + 1];
13534
+ return from === undefined || to === undefined || to < from ? undefined : [from, to];
13535
+ };
13536
+ const prose = [];
13537
+ let block;
13538
+ for (const token of tokenStream.getTokens()) {
13539
+ const name = vocabulary.getSymbolicName(token.type);
13540
+ const range = rangeOf(token);
13541
+ if (name === "BLOCK_ANNOTATION_TEXT" || name === "BLOCK_ANNOTATION_END") {
13542
+ if (!block || !range || token.startIndex !== block.next)
13543
+ return;
13544
+ if (name === "BLOCK_ANNOTATION_END") {
13545
+ block = undefined;
13546
+ } else {
13547
+ if (block.prose)
13548
+ prose.push(range);
13549
+ block.next = token.stopIndex + 1;
13550
+ }
13551
+ continue;
13552
+ }
13553
+ if (block)
13554
+ return;
13555
+ if (name === "BLOCK_ANNOTATION_BEGIN" || name === "DOC_BLOCK_ANNOTATION_BEGIN") {
13556
+ if (!range)
13557
+ return;
13558
+ block = {
13559
+ prose: PROSE_ROUTES.has(noteRoute(text.slice(...range)) ?? ""),
13560
+ next: token.stopIndex + 1
13561
+ };
13562
+ } else if (name === "ANNOTATION" || name === "DOC_ANNOTATION") {
13563
+ if (!range)
13564
+ return;
13565
+ if (PROSE_ROUTES.has(noteRoute(text.slice(...range)) ?? ""))
13566
+ prose.push([range[0] + 1, range[1]]);
13567
+ }
13568
+ }
13569
+ return block ? undefined : prose;
13570
+ } catch {
13571
+ return;
13572
+ }
13573
+ }
13574
+ function hasCallerAuthorizeAnnotation(callerText, precedingText = "") {
13575
+ if (!AUTHORIZE_ANNOTATION_ANYWHERE.test(callerText))
13576
+ return false;
13577
+ const text = precedingText + callerText;
13578
+ const bodies = proseRanges(text);
13579
+ lastRefusalKind = bodies !== undefined ? "lexed" : lexerMissing ? "no_lexer" : "whole_text";
13580
+ if (bodies === undefined)
13581
+ return true;
13582
+ const sticky = new RegExp(AUTHORIZE_TAG_LIKE, "iuy");
13583
+ const callerStart = precedingText.length;
13584
+ const from = text.lastIndexOf(`
13585
+ `, callerStart - 1) + 1;
13586
+ let body = 0;
13587
+ for (let at = text.indexOf("#", from);at !== -1; at = text.indexOf("#", at + 1)) {
13588
+ sticky.lastIndex = at;
13589
+ const hit = sticky.exec(text);
13590
+ if (!hit)
13591
+ continue;
13592
+ const end = at + hit[0].length;
13593
+ if (end <= callerStart)
13594
+ continue;
13595
+ while (body < bodies.length && bodies[body][1] <= at)
13596
+ body++;
13597
+ const [start, stop] = bodies[body] ?? [Infinity, -Infinity];
13598
+ if (!(start <= at && end <= stop))
13599
+ return true;
13600
+ }
13601
+ return false;
13602
+ }
13603
+ function assertNoCallerAuthorizeAnnotation(callerText, precedingText = "") {
13604
+ if (hasCallerAuthorizeAnnotation(callerText, precedingText))
13605
+ refuseCallerAuthorizeAnnotation();
13471
13606
  }
13472
- function assertNoCallerAuthorizeAnnotation(callerText) {
13607
+ function assertNoAuthorizeTagLike(callerText) {
13473
13608
  if (!AUTHORIZE_ANNOTATION_ANYWHERE.test(callerText))
13474
13609
  return;
13610
+ lastRefusalKind = "whole_text";
13611
+ refuseCallerAuthorizeAnnotation();
13612
+ }
13613
+ function refuseCallerAuthorizeAnnotation() {
13475
13614
  throw new BadRequestError("An `authorize` annotation is not permitted in caller-submitted Malloy " + "text. Access gates are declared by the model author on the source; a " + "request cannot introduce, replace, or relax one. To validate a gate " + "you are authoring, save it to the package's model file and reload the " + "package — model load validates every `#(authorize)` annotation it " + "declares.");
13476
13615
  }
13477
13616
  function containsAuthorizeAnnotationTag(texts) {
@@ -13669,13 +13808,19 @@ ${rewrites}
13669
13808
  ` + `Test that the rewrite gates the same rows and check the row count ` + `matches what the string form served.`
13670
13809
  });
13671
13810
  }
13672
- var AUTHORIZE_TAG_LIKE, AUTHORIZE_ANNOTATION_ANYWHERE, REGEXP_SPECIAL, GIVEN_REF_PATTERN, SINGLE_QUOTE_ESCAPE, DOUBLE_QUOTE_ESCAPE, STRING_LITERAL_PATTERN, everyMemberOf = () => (...members) => members, ROW_LEVEL_GATE_REJECTION_CAUSES, WHOLE_BODY_QUOTED_STRING, WHOLE_BODY_SINGLE_QUOTED_STRING;
13811
+ var AUTHORIZE_TAG_LIKE, AUTHORIZE_ANNOTATION_ANYWHERE, REGEXP_SPECIAL, PROSE_ROUTES, makeMalloyParser, parserLoader, warnedNoParser = false, lastLexed, lastRefusalKind = "lexed", lexerMissing = false, GIVEN_REF_PATTERN, SINGLE_QUOTE_ESCAPE, DOUBLE_QUOTE_ESCAPE, STRING_LITERAL_PATTERN, everyMemberOf = () => (...members) => members, ROW_LEVEL_GATE_REJECTION_CAUSES, WHOLE_BODY_QUOTED_STRING, WHOLE_BODY_SINGLE_QUOTED_STRING;
13673
13812
  var init_authorize = __esm(() => {
13674
13813
  init_errors();
13675
13814
  init_authorize_routes();
13676
- AUTHORIZE_TAG_LIKE = String.raw`##?\|?[ \t]*[([{<]?[ \t]*(?:(?:(?:row|source)[-_]?)?authorize|access[-_]?filter)(?=[)\]}>]|[ \t]|$)`;
13815
+ AUTHORIZE_TAG_LIKE = String.raw`##?\|?[ \t]*(?:[([{<][ \t]*)?(?:(?:(?:row|source)[-_]?)?authorize|access[-_]?filter)(?=[)\]}>]|[ \t]|$)`;
13677
13816
  AUTHORIZE_ANNOTATION_ANYWHERE = new RegExp(AUTHORIZE_TAG_LIKE, "iu");
13678
13817
  REGEXP_SPECIAL = /[.*+?^${}()|[\]\\]/g;
13818
+ PROSE_ROUTES = new Set(["markdown", "text"]);
13819
+ parserLoader = loadMalloyParser;
13820
+ lastLexed = {
13821
+ text: "",
13822
+ prose: []
13823
+ };
13679
13824
  GIVEN_REF_PATTERN = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
13680
13825
  SINGLE_QUOTE_ESCAPE = /\\(['\\])/g;
13681
13826
  DOUBLE_QUOTE_ESCAPE = /\\(["\\])/g;
@@ -13688,9 +13833,12 @@ var init_authorize = __esm(() => {
13688
13833
  // src/authorize_metrics.ts
13689
13834
  function recordAuthorizeGuardRejection(field) {
13690
13835
  guardRejectionCounter ??= publisherMeter().createCounter("publisher_authorize_guard_rejected_total", {
13691
- description: "Requests rejected with 400 for declaring an `#(authorize)` annotation in caller-submitted Malloy text. Label: field ('query'|'source_name'|'query_name'|'compile_source')."
13836
+ description: "Requests rejected with 400 for declaring an `#(authorize)` annotation in caller-submitted Malloy text. Labels: field ('query'|'source_name'|'query_name'|'compile_source'), match ('lexed' when Malloy's lexer placed a tag outside prose, 'whole_text' when the text could not be lexed cleanly or is a name, 'no_lexer' when Malloy's parser failed to load so every prose-bearing text refuses)."
13837
+ });
13838
+ guardRejectionCounter.add(1, {
13839
+ field,
13840
+ match: lastCallerGuardRefusalKind()
13692
13841
  });
13693
- guardRejectionCounter.add(1, { field });
13694
13842
  }
13695
13843
  function recordAuthorizeBypass(entryPoint) {
13696
13844
  bypassCounter ??= publisherMeter().createCounter("publisher_authorize_bypass_total", {
@@ -14817,6 +14965,44 @@ var init_gate_classification = __esm(() => {
14817
14965
  init_gate_registry_walk();
14818
14966
  });
14819
14967
 
14968
+ // src/service/model_info.ts
14969
+ import { modelDefToModelInfo } from "@malloydata/malloy";
14970
+ function modelInfoOf(modelDef) {
14971
+ try {
14972
+ return modelDefToModelInfo(modelDef);
14973
+ } catch (err) {
14974
+ if (!isUnboundGiven(err) || !modelDef.givens)
14975
+ throw err;
14976
+ try {
14977
+ return modelDefToModelInfo({
14978
+ ...modelDef,
14979
+ givens: withPlaceholderDefaults(modelDef.givens)
14980
+ });
14981
+ } catch {
14982
+ throw err;
14983
+ }
14984
+ }
14985
+ }
14986
+ function isUnboundGiven(err) {
14987
+ return err?.code === "compiler-given-no-value";
14988
+ }
14989
+ function withPlaceholderDefaults(givens) {
14990
+ const out = {};
14991
+ for (const [id, given] of Object.entries(givens)) {
14992
+ if (given.default !== undefined) {
14993
+ out[id] = given;
14994
+ continue;
14995
+ }
14996
+ const isFilter = given.type.type === "filter expression";
14997
+ out[id] = {
14998
+ ...given,
14999
+ default: isFilter ? { node: "filterLiteral", filterSrc: "" } : { node: "null" }
15000
+ };
15001
+ }
15002
+ return out;
15003
+ }
15004
+ var init_model_info = () => {};
15005
+
14820
15006
  // src/service/query_text.ts
14821
15007
  import { ParseUtil } from "@malloydata/malloy-tag";
14822
15008
  function extractRunTargetSourceName(query) {
@@ -15748,6 +15934,17 @@ function isNotebookModelPath(modelPath) {
15748
15934
  const segments = modelPath.split("/");
15749
15935
  return segments.length === 2 && segments[0] === NOTEBOOKS_DIR;
15750
15936
  }
15937
+ function isDocumentModelPath(modelPath) {
15938
+ if (!modelPath.endsWith(MODEL_FILE_SUFFIX))
15939
+ return false;
15940
+ const segments = modelPath.split("/");
15941
+ return segments.length === 2 && (segments[0] === NOTEBOOKS_DIR || segments[0] === DASHBOARDS_DIR);
15942
+ }
15943
+ function documentKind(modelPath, tagKind) {
15944
+ if (tagKind === "notebook" || tagKind === "dashboard")
15945
+ return tagKind;
15946
+ return isNotebookModelPath(modelPath) ? "notebook" : "dashboard";
15947
+ }
15751
15948
  function routeOfNote(text) {
15752
15949
  return routeOf3({ value: text.trimStart() });
15753
15950
  }
@@ -15775,34 +15972,71 @@ function dashboardDescriptionNotes(notes) {
15775
15972
  return notes.filter((n) => line !== undefined && (n.at?.range.start.line ?? 0) > line).map((n) => n.text);
15776
15973
  }
15777
15974
  function hasArtifactLineOutsideBlocks(source, artifactLine) {
15975
+ return findArtifactLineOutsideBlocks(source, artifactLine) !== undefined;
15976
+ }
15977
+ function blockCloserFinder(lines) {
15978
+ const byKey = new Map;
15979
+ lines.forEach((line, at) => {
15980
+ const indent = /^[ \t]*/.exec(line)?.[0].length ?? 0;
15981
+ for (const closer of ["|##", "|#"])
15982
+ if (closesBlock(line, 0, indent, closer)) {
15983
+ const key = `${indent}${closer}`;
15984
+ const ats = byKey.get(key);
15985
+ if (ats)
15986
+ ats.push(at);
15987
+ else
15988
+ byKey.set(key, [at]);
15989
+ break;
15990
+ }
15991
+ });
15992
+ const cursor = new Map;
15993
+ return (column, closer, after) => {
15994
+ const key = `${column}${closer}`;
15995
+ const ats = byKey.get(key) ?? [];
15996
+ let k = cursor.get(key) ?? 0;
15997
+ while (k < ats.length && ats[k] <= after)
15998
+ k++;
15999
+ cursor.set(key, k);
16000
+ return ats[k] ?? -1;
16001
+ };
16002
+ }
16003
+ function findArtifactLineOutsideBlocks(source, artifactLine) {
15778
16004
  const lines = source.split(/\r\n|\r|\n/);
16005
+ const closerAfter = blockCloserFinder(lines);
15779
16006
  for (let i = 0;i < lines.length; i++) {
15780
16007
  const trimmed = lines[i].trimStart();
15781
16008
  const opener = /^(#{1,2})\|/.exec(trimmed);
15782
16009
  if (opener) {
15783
- const wanted = `|${opener[1]}`;
15784
- if (trimmed.includes(wanted, opener[0].length))
15785
- continue;
15786
16010
  const column = lines[i].length - trimmed.length;
15787
- let end = -1;
15788
- for (let at = i + 1;at < lines.length; at++)
15789
- if (closesBlock(lines[at], 0, column, wanted)) {
15790
- end = at;
15791
- break;
15792
- }
16011
+ const end = closerAfter(column, `|${opener[1]}`, i);
15793
16012
  if (end !== -1) {
16013
+ const block = [trimmed, ...lines.slice(i + 1, end)].join(`
16014
+ `);
16015
+ if (artifactLine.test(block))
16016
+ return block;
15794
16017
  i = end;
15795
16018
  continue;
15796
16019
  }
15797
16020
  }
15798
16021
  if (artifactLine.test(trimmed))
15799
- return true;
16022
+ return trimmed;
15800
16023
  }
15801
- return false;
16024
+ return;
16025
+ }
16026
+ function artifactKindOfNotes(notes) {
16027
+ const tag = motlyTag(notes.map((note) => note.text).filter(isArtifactNoteText));
16028
+ return tagText(tag?.tag("artifact"), "kind");
15802
16029
  }
15803
16030
  function claimsToBeANotebook(source) {
15804
16031
  return hasArtifactLineOutsideBlocks(source, ARTIFACT_NOTE);
15805
16032
  }
16033
+ function artifactTagText(source) {
16034
+ return findArtifactLineOutsideBlocks(source, ARTIFACT_NOTE);
16035
+ }
16036
+ function artifactKindInText(source) {
16037
+ const line = artifactTagText(source);
16038
+ return line === undefined ? undefined : tagText(motlyTag([line])?.tag("artifact"), "kind");
16039
+ }
15806
16040
  function parseNotebookText(text) {
15807
16041
  try {
15808
16042
  const { problems, parse } = translateToParse(text);
@@ -16239,9 +16473,12 @@ function readMarkdownBlocks(parse, text) {
16239
16473
  return blocks;
16240
16474
  }
16241
16475
  function notebookReaderProblem(modelPath, text, modelDef, url) {
16242
- if (!isNotebookModelPath(modelPath))
16476
+ if (!isDocumentModelPath(modelPath))
16477
+ return;
16478
+ const notes = ownModelNoteObjects(modelDef);
16479
+ if (artifactNoteLine(notes) === undefined)
16243
16480
  return;
16244
- if (artifactNoteLine(ownModelNoteObjects(modelDef)) === undefined)
16481
+ if (documentKind(modelPath, artifactKindOfNotes(notes)) !== "notebook")
16245
16482
  return;
16246
16483
  const parse = parseNotebookText(text);
16247
16484
  const error = isNotebookReaderError(parse) ? parse : readNotebookCells(parse, modelDef, text).error;
@@ -16261,7 +16498,7 @@ function notebookReaderProblem(modelPath, text, modelDef, url) {
16261
16498
  }
16262
16499
  };
16263
16500
  }
16264
- var NOTEBOOKS_DIR = "notebooks", ARTIFACT_NOTE, MARKDOWN_ROUTE = "markdown", attachedNowhereFix = (block) => block ? "write it as a floating `##|(markdown)` block closed by `|##` for prose that stands on its own, or move it directly above the statement it describes." : "write it as a floating `##(markdown)` line for prose that stands on its own, or move it directly above the statement it describes.", isRuleNode = (node) => node !== undefined && node.ruleIndex !== undefined && (node.childCount ?? 0) > 0, callAccessor = (node, name) => {
16501
+ var NOTEBOOKS_DIR = "notebooks", DASHBOARDS_DIR = "dashboards", ARTIFACT_NOTE, ANY_ARTIFACT_NOTE, MARKDOWN_ROUTE = "markdown", attachedNowhereFix = (block) => block ? "write it as a floating `##|(markdown)` block closed by `|##` for prose that stands on its own, or move it directly above the statement it describes." : "write it as a floating `##(markdown)` line for prose that stands on its own, or move it directly above the statement it describes.", isRuleNode = (node) => node !== undefined && node.ruleIndex !== undefined && (node.childCount ?? 0) > 0, callAccessor = (node, name) => {
16265
16502
  const accessor = node[name];
16266
16503
  return typeof accessor === "function" ? accessor.call(node) : undefined;
16267
16504
  }, STATEMENT_ACCESSORS, NOTEBOOK_PARSE_URL = "file:///publisher-notebook-reader/notebook.malloy", NOTE_TOKENS, TEXT_BLOCK_NAME;
@@ -16270,7 +16507,62 @@ var init_notebook = __esm(() => {
16270
16507
  init_annotations();
16271
16508
  init_query_text();
16272
16509
  init_motly();
16273
- ARTIFACT_NOTE = /^##[ \t]*artifact\b/;
16510
+ ARTIFACT_NOTE = {
16511
+ test(note) {
16512
+ const prefix = /^##\|?(?:[ \t\r\n]|$)/.exec(note);
16513
+ if (!prefix)
16514
+ return false;
16515
+ const bare = /[0-9A-Za-z_\u00C0-\u024F\u1E00-\u1EFF]+/y;
16516
+ let depth = 0;
16517
+ let before = "";
16518
+ for (let i = prefix[0].length;i < note.length; i++) {
16519
+ const c = note[i];
16520
+ if (/\s/.test(c))
16521
+ continue;
16522
+ if (note.startsWith("-...", i)) {
16523
+ i += 3;
16524
+ before = "";
16525
+ continue;
16526
+ }
16527
+ bare.lastIndex = i;
16528
+ const word = bare.exec(note)?.[0];
16529
+ if (c === '"' || c === "'") {
16530
+ const fence = note.startsWith(c.repeat(3), i) ? c.repeat(3) : c;
16531
+ i += fence.length;
16532
+ while (i < note.length && !note.startsWith(fence, i))
16533
+ i += note[i] === "\\" ? 2 : 1;
16534
+ i += fence.length - 1;
16535
+ } else if (c === "`") {
16536
+ let end = i + 1;
16537
+ while (end < note.length && note[end] !== "`" && note[end] !== `
16538
+ `)
16539
+ end += note[end] === "\\" ? 2 : 1;
16540
+ const name = note.slice(i + 1, end);
16541
+ if (depth === 0 && name === "artifact" && !/[=.-]/.test(before))
16542
+ return true;
16543
+ i = end;
16544
+ } else if (c === "#") {
16545
+ const eol = note.indexOf(`
16546
+ `, i);
16547
+ if (eol < 0)
16548
+ return false;
16549
+ i = eol;
16550
+ continue;
16551
+ } else if (c === "{" || c === "[")
16552
+ depth++;
16553
+ else if (c === "}" || c === "]")
16554
+ depth--;
16555
+ else if (word) {
16556
+ if (depth === 0 && word === "artifact" && !/[=.-]/.test(before))
16557
+ return true;
16558
+ i += word.length - 1;
16559
+ }
16560
+ before = c;
16561
+ }
16562
+ return false;
16563
+ }
16564
+ };
16565
+ ANY_ARTIFACT_NOTE = /^#{1,2}\|?(?=[ \t\r\n])(?:[^"'\\]|\\.|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')*?(?<![\w\u00C0-\u024F\u1E00-\u1EFF.$-])artifact(?![\w\u00C0-\u024F\u1E00-\u1EFF])/;
16274
16566
  STATEMENT_ACCESSORS = [
16275
16567
  ["runStatement", "run"],
16276
16568
  ["docAnnotations", "notes"],
@@ -23088,9 +23380,12 @@ var init_given = __esm(() => {
23088
23380
  });
23089
23381
 
23090
23382
  // src/service/dashboard.ts
23091
- import { isSourceDef as isSourceDef5 } from "@malloydata/malloy";
23383
+ import { isJoined as isJoined3, isSourceDef as isSourceDef5 } from "@malloydata/malloy";
23384
+ function queryTiles(tiles) {
23385
+ return (tiles ?? []).filter((tile) => tile.kind === "query");
23386
+ }
23092
23387
  function filterPublisherOwnedRenderLogs(logs, modelPath) {
23093
- const couldBeATile = isDashboardModelPath(modelPath);
23388
+ const couldBeATile = isDocumentModelPath(modelPath);
23094
23389
  return logs.filter((log) => {
23095
23390
  const match = UNKNOWN_RENDER_TAG.exec(log.message ?? "");
23096
23391
  if (!match)
@@ -23115,6 +23410,24 @@ function dashboardSlug(modelPath) {
23115
23410
  function matchesDocumentedDashboardName(slug) {
23116
23411
  return /^[a-zA-Z0-9_-]+$/.test(slug);
23117
23412
  }
23413
+ function readTileEntries(artifact) {
23414
+ return artifact.array("tiles")?.flatMap((entry) => {
23415
+ const text = tagText(entry);
23416
+ if (text === undefined)
23417
+ return [];
23418
+ if (tagText(entry, "kind") !== "text")
23419
+ return [{ kind: "query", expression: text }];
23420
+ const colspan = positiveInteger(tagNumeric(entry, "colspan"));
23421
+ return [
23422
+ {
23423
+ kind: "text",
23424
+ name: text,
23425
+ ...colspan !== undefined && { colspan },
23426
+ ...entry.has("break") && { break: true }
23427
+ }
23428
+ ];
23429
+ });
23430
+ }
23118
23431
  function readArtifactTag(tag, declaredType) {
23119
23432
  const artifact = tag.tag("artifact");
23120
23433
  if (!artifact)
@@ -23123,12 +23436,25 @@ function readArtifactTag(tag, declaredType) {
23123
23436
  const autorun = readAutorun(artifact);
23124
23437
  return {
23125
23438
  title: tagText(artifact, "title"),
23126
- tiles: artifact.array("tiles")?.filter((tile) => tagText(tile, "kind") !== "text").map((tile) => tagText(tile)).filter((tile) => tile !== undefined),
23439
+ kind: tagText(artifact, "kind"),
23440
+ tiles: readTileEntries(artifact),
23127
23441
  dashboardColumns: tag.tag("dashboard")?.has("columns") ? positiveInteger(tagNumeric(tag.tag("dashboard"), "columns")) : artifact.array("tiles") ? positiveInteger(tagNumeric(artifact, "dashboard_columns")) : undefined,
23128
23442
  givens,
23129
23443
  autorun
23130
23444
  };
23131
23445
  }
23446
+ function readTextBlocks(notes) {
23447
+ return notes.flatMap((note) => {
23448
+ const newline = note.indexOf(`
23449
+ `);
23450
+ const opener = parseMarkdownOpener(newline === -1 ? note : note.slice(0, newline));
23451
+ if (opener?.level !== 2 || !opener.name)
23452
+ return [];
23453
+ const body = newline === -1 ? "" : note.slice(newline + 1);
23454
+ return [{ name: opener.name, body: body.replace(/\r\n?/g, `
23455
+ `) }];
23456
+ });
23457
+ }
23132
23458
  function positiveInteger(value) {
23133
23459
  return value !== undefined && Number.isInteger(value) && value >= 1 ? value : undefined;
23134
23460
  }
@@ -23176,6 +23502,11 @@ function readDashboardModelFacts(modelPath, modelDef, surfacedGivenNames, gatesB
23176
23502
  for (const field of obj.fields) {
23177
23503
  const fieldName = field.as || field.name;
23178
23504
  fieldNames.add(fieldName);
23505
+ if (isJoined3(field)) {
23506
+ for (const joined of field.fields) {
23507
+ fieldNames.add(`${fieldName}.${joined.as || joined.name}`);
23508
+ }
23509
+ }
23179
23510
  if (field.type === "turtle") {
23180
23511
  const refs = new Set(sourceGivens);
23181
23512
  collectGivenRefs(field.pipeline, refs);
@@ -23246,7 +23577,11 @@ function tagTextArray(tag, ...path) {
23246
23577
  return items?.map((item) => tagText(item)).filter(isString);
23247
23578
  }
23248
23579
  function resolveTileGivens(tile, facts) {
23249
- return referencedTileGivens(tile, facts)?.filter((name) => facts.givens.has(name));
23580
+ const reads = referencedTileGivens(tile, facts);
23581
+ if (reads === undefined)
23582
+ return;
23583
+ const gateReads = facts.compiledTileGivens?.get(normalizeTileExpression(tile))?.gateReads ?? [];
23584
+ return Array.from(new Set([...reads, ...gateReads])).filter((name) => facts.givens.has(name));
23250
23585
  }
23251
23586
  function resolveTileLayout(tile, facts) {
23252
23587
  const annotations = tileAnnotations(tile, facts);
@@ -23258,7 +23593,15 @@ function tileAnnotations(tile, facts) {
23258
23593
  }
23259
23594
  function referencedTileGivens(tile, facts) {
23260
23595
  const normalized = normalizeTileExpression(tile);
23261
- return facts.viewGivens.get(normalized) ?? facts.queries.find((query) => query.name === normalized)?.givens;
23596
+ const statically = facts.viewGivens.get(normalized) ?? facts.queries.find((query) => query.name === normalized)?.givens;
23597
+ const compiled = facts.compiledTileGivens?.get(normalized)?.reads;
23598
+ if (!compiled)
23599
+ return statically;
23600
+ const rank = (name) => {
23601
+ const at = statically?.indexOf(name) ?? -1;
23602
+ return at === -1 ? Number.MAX_SAFE_INTEGER : at;
23603
+ };
23604
+ return [...compiled].sort((a, b) => rank(a) - rank(b));
23262
23605
  }
23263
23606
  function buildGivenSpecs(names, facts) {
23264
23607
  const declarations = facts.givens;
@@ -23300,26 +23643,41 @@ function buildDashboardManifest(facts) {
23300
23643
  const modelTag = motlyTag(facts.modelAnnotations);
23301
23644
  const declaredType = (name2) => facts.givens.get(name2)?.type;
23302
23645
  const composite = modelTag ? readArtifactTag(modelTag, declaredType) : undefined;
23303
- if (composite?.tiles?.length) {
23304
- const tiles = composite.tiles.map((query) => {
23646
+ const kind = documentKind(facts.modelPath, composite?.kind);
23647
+ if (composite?.tiles && (composite.tiles.length > 0 || kind === "notebook")) {
23648
+ const blocks = readTextBlocks(facts.modelAnnotations);
23649
+ const tiles = composite.tiles.map((entry) => {
23650
+ if (entry.kind === "text") {
23651
+ return {
23652
+ kind: "text",
23653
+ name: entry.name,
23654
+ markdown: blocks.find((block) => block.name === entry.name)?.body ?? "",
23655
+ ...entry.colspan !== undefined && { colspan: entry.colspan },
23656
+ ...entry.break && { break: true }
23657
+ };
23658
+ }
23659
+ const query = entry.expression;
23305
23660
  const givenNames = resolveTileGivens(query, facts);
23306
23661
  const layout = resolveTileLayout(query, facts);
23307
23662
  return {
23663
+ kind: "query",
23308
23664
  query,
23309
23665
  ...givenNames ? { givenNames } : {},
23310
23666
  ...layout
23311
23667
  };
23312
23668
  });
23669
+ const queries = queryTiles(tiles);
23313
23670
  const doc = docCommentTitleAndDescription(facts.descriptionNotes ?? facts.modelAnnotations, composite.title);
23314
23671
  return {
23315
23672
  ...base,
23673
+ kind,
23316
23674
  title: doc.title ?? name,
23317
23675
  description: doc.description,
23318
23676
  tiles,
23319
- dashboardColumns: composite.dashboardColumns,
23677
+ dashboardColumns: kind === "notebook" ? 1 : composite.dashboardColumns,
23320
23678
  startingGivens: composite.givens,
23321
23679
  autorun: composite.autorun,
23322
- givens: buildGivenSpecs(tiles.some((tile) => tile.givenNames === undefined) ? Array.from(facts.givens.keys()) : tiles.flatMap((tile) => tile.givenNames ?? []), facts)
23680
+ givens: buildGivenSpecs(queries.some((tile) => tile.givenNames === undefined) ? Array.from(facts.givens.keys()) : queries.flatMap((tile) => (referencedTileGivens(tile.query, facts) ?? []).filter((name2) => facts.givens.has(name2))), facts)
23323
23681
  };
23324
23682
  }
23325
23683
  for (const query of facts.queries) {
@@ -23330,6 +23688,7 @@ function buildDashboardManifest(facts) {
23330
23688
  const doc = docCommentTitleAndDescription(query.annotations, artifact.title);
23331
23689
  return {
23332
23690
  ...base,
23691
+ kind: "dashboard",
23333
23692
  title: doc.title ?? name,
23334
23693
  description: doc.description,
23335
23694
  query: query.name,
@@ -23371,19 +23730,33 @@ function lintDashboard(facts, manifest) {
23371
23730
  continue;
23372
23731
  add(`\`${property}\` in the artifact tag does nothing in Publisher` + (ARTIFACT_PROPERTY_REPLACEMENTS[property] ? `. ${ARTIFACT_PROPERTY_REPLACEMENTS[property]}` : property === "tiles" ? `: a dashboard whose tag sits on a query is that one query's ` + `result. Move the tag to model level (\`## artifact\`) to ` + `combine separate queries into a grid.` : `.`));
23373
23732
  }
23733
+ const textBlocks = readTextBlocks(facts.modelAnnotations);
23374
23734
  for (const entry of artifactTag?.array("tiles") ?? []) {
23375
23735
  const kind = tagText(entry, "kind");
23376
- if (kind === "text") {
23377
- add(`\`${tagText(entry) ?? "a tile"}\` in \`tiles=[…]\` is a text tile, ` + `but Publisher does not render text tiles yet, so it is left out ` + `of the page.`);
23736
+ const label = tagText(entry) ?? "a tile";
23737
+ const read = kind === "text" ? ["kind", "colspan", "break"] : [];
23738
+ const carried = Object.keys(entry.dict ?? {}).filter((property) => !read.includes(property) && !(property === "kind" && kind === "query"));
23739
+ if (carried.length > 0) {
23740
+ const named = carried.map((property) => `\`${property}\``).join(", ");
23741
+ add(kind === "text" ? `\`${label}\` in \`tiles=[…]\` carries ${named}, which Publisher does not read: a text tile's entry reads only \`colspan\` and \`break\`.` : `\`${label}\` in \`tiles=[…]\` carries ${named}, ` + `which Publisher does not read: a tile entry is the run expression ` + `alone. Per-tile presentation goes on the view the tile names ` + `(\`# colspan\`, \`# break\`, \`# label\`, \`# subtitle\`, ` + `\`# borderless\`).`);
23742
+ }
23743
+ if (kind !== "text")
23378
23744
  continue;
23745
+ if (!textBlocks.some((block) => block.name === label)) {
23746
+ add(`text tile \`${label}\` has no \`##|(markdown) ${label}\` block, so it shows nothing. ` + `Fix: write that block, with the text on the lines below its opener and \`|##\` after.`);
23747
+ }
23748
+ if (entry.has("colspan") && positiveInteger(tagNumeric(entry, "colspan")) === undefined) {
23749
+ add(`colspan on text tile \`${label}\` must be a positive integer. The tile spans one column.`);
23379
23750
  }
23380
- const carried = Object.keys(entry.dict ?? {}).filter((property) => !(property === "kind" && kind === "query"));
23381
- if (carried.length === 0)
23382
- continue;
23383
- const named = carried.map((property) => `\`${property}\``).join(", ");
23384
- add(`\`${tagText(entry) ?? "a tile"}\` in \`tiles=[…]\` carries ${named}, ` + `which Publisher does not read: a tile entry is the run expression ` + `alone. Per-tile presentation goes on the view the tile names ` + `(\`# colspan\`, \`# break\`, \`# label\`, \`# subtitle\`, ` + `\`# borderless\`).`);
23385
23751
  }
23386
- for (const { query: tile } of manifest.tiles ?? []) {
23752
+ const seenBlocks = new Set;
23753
+ for (const { name } of textBlocks) {
23754
+ if (seenBlocks.has(name)) {
23755
+ add(`the \`##|(markdown) ${name}\` block is written twice, and only the first is shown. Fix: rename or delete the second.`);
23756
+ }
23757
+ seenBlocks.add(name);
23758
+ }
23759
+ for (const { query: tile } of manifest.kind === "notebook" ? [] : queryTiles(manifest.tiles)) {
23387
23760
  const tag = motlyTag(tileAnnotations(tile, facts) ?? []);
23388
23761
  if (!tag?.has("colspan"))
23389
23762
  continue;
@@ -23402,21 +23775,28 @@ function lintDashboard(facts, manifest) {
23402
23775
  }
23403
23776
  const columnsTag = ownTag?.tag("dashboard");
23404
23777
  if (columnsTag?.has("columns")) {
23405
- if (positiveInteger(tagNumeric(columnsTag, "columns")) === undefined) {
23778
+ const width = positiveInteger(tagNumeric(columnsTag, "columns"));
23779
+ if (width === undefined) {
23406
23780
  const raw = tagText(columnsTag, "columns");
23407
23781
  add(`# dashboard { columns=… } must be a positive integer, got ` + `${raw === undefined ? "a value that could not be read" : JSON.stringify(raw)}. ` + `The grid falls back to the default.`);
23782
+ } else if (width > MAX_BUILDER_COLUMNS) {
23783
+ add(`# dashboard { columns=${width} } is wider than the builder offers (${MAX_BUILDER_COLUMNS}); the reader still renders it.`);
23408
23784
  }
23409
23785
  }
23410
- for (const { query: tile } of manifest.tiles ?? []) {
23786
+ for (const { query: tile } of queryTiles(manifest.tiles)) {
23411
23787
  const parts = plainTileParts(tile);
23412
23788
  if (!parts)
23413
23789
  continue;
23414
23790
  const resolved = referencedTileGivens(tile, facts) !== undefined || "name" in parts && facts.queries.some((query) => query.name === parts.name);
23791
+ if (!resolved && "name" in parts && textBlocks.some((block) => block.name === parts.name)) {
23792
+ add(`tile "${tile}" names the ##|(markdown) ${tile} block but has no kind=text, so it is read as a query. Fix: write ${tile} { kind=text }`, "error");
23793
+ continue;
23794
+ }
23415
23795
  if (!resolved) {
23416
23796
  const detail = "source" in parts ? facts.sourceFields.has(parts.source) ? `source "${parts.source}" has no view "${parts.view}"` : `no source "${parts.source}" in this file` : `no query named "${parts.name}" in this file`;
23417
23797
  add(`tile "${tile}" does not resolve: ${detail}.`, "error");
23418
23798
  }
23419
- const unbindable = (referencedTileGivens(tile, facts) ?? []).filter((name) => !facts.givens.has(name));
23799
+ const unbindable = Array.from(new Set(referencedTileGivens(tile, facts) ?? [])).filter((name) => !facts.givens.has(name));
23420
23800
  for (const name of unbindable) {
23421
23801
  add(`tile "${tile}" filters by given "${name}", which this file does ` + `not import, so no control is shown for it and it stays at its ` + `default. Add it to an import in ${facts.modelPath}.`);
23422
23802
  }
@@ -23579,7 +23959,7 @@ function lintSelfDrills(facts) {
23579
23959
  }
23580
23960
  return Array.from(findings.values());
23581
23961
  }
23582
- var DASHBOARDS_DIR = "dashboards", COMPONENT_FILE_SUFFIXES, MODEL_FILE_SUFFIX2 = ".malloy", PUBLISHER_OWNED_TAGS, RENDERER_CHILD_TAGS, UNKNOWN_RENDER_TAG, COMPOSITE_ARTIFACT_PROPERTIES, QUERY_ARTIFACT_PROPERTIES, ARTIFACT_PROPERTY_REPLACEMENTS, NAME_THE_TILES = `Name the views to show: tiles=["source -> view", …].`;
23962
+ var MAX_BUILDER_COLUMNS = 24, COMPONENT_FILE_SUFFIXES, MODEL_FILE_SUFFIX2 = ".malloy", PUBLISHER_OWNED_TAGS, RENDERER_CHILD_TAGS, UNKNOWN_RENDER_TAG, COMPOSITE_ARTIFACT_PROPERTIES, QUERY_ARTIFACT_PROPERTIES, ARTIFACT_PROPERTY_REPLACEMENTS, NAME_THE_TILES = `Name the views to show: tiles=["source -> view", …].`;
23583
23963
  var init_dashboard = __esm(() => {
23584
23964
  init_annotations();
23585
23965
  init_notebook();
@@ -23800,9 +24180,8 @@ var init_filter = __esm(() => {
23800
24180
 
23801
24181
  // src/service/source_extraction.ts
23802
24182
  import {
23803
- isJoined as isJoined3,
23804
- isSourceDef as isSourceDef6,
23805
- modelDefToModelInfo
24183
+ isJoined as isJoined4,
24184
+ isSourceDef as isSourceDef6
23806
24185
  } from "@malloydata/malloy";
23807
24186
  function joinFieldNamesUnresolvableDeclaration(field, modelDef) {
23808
24187
  const ids = [field.referenceID, field.sourceID].filter((id) => !!id);
@@ -23833,7 +24212,7 @@ function considerNoteOwner(declaredBy, note, candidate) {
23833
24212
  function collectSourceInfos(modelDef) {
23834
24213
  const names = Object.keys(modelDef.contents).filter((name) => isSourceDef6(modelDef.contents[name]));
23835
24214
  const exports = [...new Set([...modelDef.exports, ...names])];
23836
- return modelDefToModelInfo({ ...modelDef, exports }).entries.filter((entry) => entry.kind === "source");
24215
+ return modelInfoOf({ ...modelDef, exports }).entries.filter((entry) => entry.kind === "source");
23837
24216
  }
23838
24217
  function extractSourcesFromModelDef(modelDef, givens, onParseError) {
23839
24218
  const filterMap = new Map;
@@ -23971,7 +24350,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
23971
24350
  continue;
23972
24351
  }
23973
24352
  const fieldName = field.as || field.name;
23974
- if (isJoined3(field) && isSourceDef6(field)) {
24353
+ if (isJoined4(field) && isSourceDef6(field)) {
23975
24354
  const joinedStruct = field;
23976
24355
  if (joinFieldNamesUnresolvableDeclaration(joinedStruct, modelDef)) {
23977
24356
  continue;
@@ -24029,6 +24408,7 @@ var init_source_extraction = __esm(() => {
24029
24408
  init_annotations();
24030
24409
  init_authorize();
24031
24410
  init_authorize_routes();
24411
+ init_model_info();
24032
24412
  init_filter();
24033
24413
  init_gate_registry_walk();
24034
24414
  });
@@ -24065,12 +24445,12 @@ init_error_wire();
24065
24445
  init_authorize();
24066
24446
  init_gate_classification();
24067
24447
  init_gate_dimension();
24448
+ init_model_info();
24068
24449
  var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
24069
24450
  import {
24070
24451
  contextOverlay,
24071
24452
  isSourceDef as isSourceDef7,
24072
24453
  MalloyConfig,
24073
- modelDefToModelInfo as modelDefToModelInfo2,
24074
24454
  Runtime
24075
24455
  } from "@malloydata/malloy";
24076
24456
  import {
@@ -24655,7 +25035,7 @@ async function compileMalloyModel(job, malloyConfig, modelPath) {
24655
25035
  modelPath,
24656
25036
  modelType: "model",
24657
25037
  modelDef,
24658
- modelInfo: modelDefToModelInfo2(modelDef),
25038
+ modelInfo: modelInfoOf(modelDef),
24659
25039
  sourceInfos,
24660
25040
  sources,
24661
25041
  queries,
@@ -24699,7 +25079,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
24699
25079
  continue;
24700
25080
  }
24701
25081
  const currentModelDef = (await localMM.getModel())._modelDef;
24702
- const currentInfo = modelDefToModelInfo2(currentModelDef);
25082
+ const currentInfo = modelInfoOf(currentModelDef);
24703
25083
  const newSources = collectSourceInfos(currentModelDef).filter((s) => !(s.name in oldSources));
24704
25084
  for (const s of newSources)
24705
25085
  oldSources[s.name] = s;
@@ -24781,7 +25161,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
24781
25161
  modelPath,
24782
25162
  modelType: "notebook",
24783
25163
  modelDef: finalModelDef,
24784
- modelInfo: finalModelDef ? modelDefToModelInfo2(finalModelDef) : undefined,
25164
+ modelInfo: finalModelDef ? modelInfoOf(finalModelDef) : undefined,
24785
25165
  sourceInfos: finalSourceInfos,
24786
25166
  sources: finalSources,
24787
25167
  queries: finalQueries,