@malloy-publisher/server 0.9.1 → 0.9.3

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 (36) hide show
  1. package/README.docker.md +14 -0
  2. package/dist/app/api-doc.yaml +414 -98
  3. package/dist/app/assets/EnvironmentPage-uo3igcP9.js +1 -0
  4. package/dist/app/assets/{HomePage-D3MvQaZn.js → HomePage-0OQVchl4.js} +1 -1
  5. package/dist/app/assets/MainPage-D4SOACdm.js +1 -0
  6. package/dist/app/assets/ModelPage-B5CpdEDl.js +2 -0
  7. package/dist/app/assets/PackagePage-BzUfTQKy.js +1 -0
  8. package/dist/app/assets/RenderedResult-DwB0-9Yx.es-1MzhmBXH.js +141 -0
  9. package/dist/app/assets/{RouteError-BQnzPJvb.js → RouteError-BXcrPjvD.js} +1 -1
  10. package/dist/app/assets/ThemeEditorPage-FpYg5NPt.js +1 -0
  11. package/dist/app/assets/{ToggleButtonGroup-CJT2JKTC.js → ToggleButtonGroup-Vkkjsbxx.js} +1 -1
  12. package/dist/app/assets/documentRoutes-BWHzvbNO.js +1 -0
  13. package/dist/app/assets/index-BZEvIUf-.js +484 -0
  14. package/dist/app/assets/index-CprgMbE_.js +1 -0
  15. package/dist/app/assets/{index-ufFZR_K-.js → index-DFvJYp4Q.js} +7 -7
  16. package/dist/app/assets/index-NVPzfbUZ.js +1317 -0
  17. package/dist/app/assets/{index-ireHUsv_.js → index-sKcoT1e_.js} +422 -427
  18. package/dist/app/assets/index.es-D2Ts8adM.js +195 -0
  19. package/dist/app/index.html +1 -1
  20. package/dist/package_load_worker.mjs +801 -80
  21. package/dist/server.mjs +21707 -17510
  22. package/package.json +15 -15
  23. package/dist/app/assets/EnvironmentPage-UMS7Py4Q.js +0 -1
  24. package/dist/app/assets/LightMode-e1lNBfpL.js +0 -1
  25. package/dist/app/assets/MainPage-B0ozqtQO.js +0 -2
  26. package/dist/app/assets/ModelPage-lQj4Axqu.js +0 -2
  27. package/dist/app/assets/PackagePage-BT0YKNKU.js +0 -1
  28. package/dist/app/assets/RenderedResult-BRgIgxWh.es-DiE7kPNX.js +0 -136
  29. package/dist/app/assets/ThemeEditorPage-D6t7w9Js.js +0 -1
  30. package/dist/app/assets/index-ByieDZlo.js +0 -23
  31. package/dist/app/assets/index-CCNqF0-G.js +0 -1
  32. package/dist/app/assets/index-CKmZ4HG7.js +0 -1322
  33. package/dist/app/assets/index-CdExa3Pk.js +0 -1
  34. package/dist/app/assets/index-TiWjmdoW.js +0 -450
  35. package/dist/app/assets/index.es-iUV0gCYk.js +0 -111
  36. package/dist/sshcrypto-cqb52edz.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, RenderTagRefusedError, UnparseableTextError, 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();
@@ -13218,6 +13218,15 @@ var init_errors = __esm(() => {
13218
13218
  };
13219
13219
  CompileRefusedError = class CompileRefusedError extends BadRequestError {
13220
13220
  };
13221
+ RenderTagRefusedError = class RenderTagRefusedError extends CompileRefusedError {
13222
+ };
13223
+ UnparseableTextError = class UnparseableTextError extends CompileRefusedError {
13224
+ detail;
13225
+ constructor(message, detail) {
13226
+ super(message);
13227
+ this.detail = detail;
13228
+ }
13229
+ };
13221
13230
  EnvironmentNotFoundError = class EnvironmentNotFoundError extends Error {
13222
13231
  constructor(message) {
13223
13232
  super(message);
@@ -13348,11 +13357,17 @@ var init_errors = __esm(() => {
13348
13357
  }
13349
13358
  };
13350
13359
  WriteRolledBackError = class WriteRolledBackError extends Error {
13351
- constructor(message) {
13352
- super(message);
13360
+ constructor(message, options) {
13361
+ super(message, options);
13353
13362
  this.name = "WriteRolledBackError";
13354
13363
  }
13355
13364
  };
13365
+ WriteVerifyError = class WriteVerifyError extends Error {
13366
+ constructor(message, options) {
13367
+ super(message, options);
13368
+ this.name = "WriteVerifyError";
13369
+ }
13370
+ };
13356
13371
  InvalidStateTransitionError = class InvalidStateTransitionError extends Error {
13357
13372
  constructor(message) {
13358
13373
  super(message);
@@ -13407,6 +13422,7 @@ var init_authorize_routes = __esm(() => {
13407
13422
 
13408
13423
  // src/service/authorize.ts
13409
13424
  import { payloadOf, routeOf } from "@malloydata/malloy";
13425
+ import { createRequire as createRequire2 } from "module";
13410
13426
  function noteRoute(text) {
13411
13427
  return routeOf({ value: text.trimStart() });
13412
13428
  }
@@ -13455,7 +13471,7 @@ function motlyAuthorizePayloadPattern(route) {
13455
13471
  return new RegExp(`^[ \\t]*[([{<][ \\t]*(?:${nearMissWordAlternation(route)})[ \\t]*[)\\]}>]`, "iu");
13456
13472
  }
13457
13473
  function malformedAuthorizeAttemptPattern(route) {
13458
- return new RegExp(`^##?\\|?[ \\t]*[([{<]?[ \\t]*(?:${nearMissWordAlternation(route)})`, "iu");
13474
+ return new RegExp(`^##?\\|?[ \\t]*(?:[([{<][ \\t]*)?(?:${nearMissWordAlternation(route)})`, "iu");
13459
13475
  }
13460
13476
  function authorizeNoteContent(text) {
13461
13477
  const route = canonicalAuthorizeRoute(noteRoute(text));
@@ -13466,12 +13482,144 @@ function authorizeNoteContent(text) {
13466
13482
  function authorizeAnnotationRoute(text) {
13467
13483
  return authorizeNoteContent(text)?.route;
13468
13484
  }
13469
- function hasCallerAuthorizeAnnotation(callerText) {
13470
- return AUTHORIZE_ANNOTATION_ANYWHERE.test(callerText);
13485
+ function loadMalloyParser() {
13486
+ try {
13487
+ const entry = createRequire2(import.meta.url).resolve("@malloydata/malloy");
13488
+ const loaded = createRequire2(entry)("./lang/run-malloy-parser");
13489
+ return typeof loaded.makeMalloyParser === "function" ? loaded.makeMalloyParser : null;
13490
+ } catch {
13491
+ return null;
13492
+ }
13493
+ }
13494
+ function malloyParserFactory() {
13495
+ if (makeMalloyParser === undefined)
13496
+ makeMalloyParser = parserLoader();
13497
+ return makeMalloyParser ?? undefined;
13498
+ }
13499
+ function lastCallerGuardRefusalKind() {
13500
+ return lastRefusalKind;
13501
+ }
13502
+ function proseRanges(text) {
13503
+ if (lastLexed.text !== text)
13504
+ lastLexed = { text, prose: lexProseRanges(text) };
13505
+ return lastLexed.prose;
13471
13506
  }
13472
- function assertNoCallerAuthorizeAnnotation(callerText) {
13507
+ function lexProseRanges(text) {
13508
+ const factory = malloyParserFactory();
13509
+ lexerMissing = !factory;
13510
+ if (!factory) {
13511
+ if (!warnedNoParser) {
13512
+ warnedNoParser = true;
13513
+ console.warn("Prose-aware caller-annotation guard disabled: @malloydata/malloy ./lang/run-malloy-parser could not be loaded; falling back to whole-text matching.");
13514
+ }
13515
+ return;
13516
+ }
13517
+ try {
13518
+ const counter = () => ({
13519
+ errors: 0,
13520
+ syntaxError() {
13521
+ this.errors++;
13522
+ }
13523
+ });
13524
+ const lexerErrors = counter();
13525
+ const parserErrors = counter();
13526
+ const { tokenStream, parser } = factory(text, {
13527
+ lexerErrorListener: lexerErrors,
13528
+ parserErrorListener: parserErrors
13529
+ });
13530
+ parser.malloyDocument();
13531
+ const vocabulary = tokenStream.tokenSource?.vocabulary;
13532
+ if (lexerErrors.errors > 0 || parserErrors.errors > 0 || typeof vocabulary?.getSymbolicName !== "function")
13533
+ return;
13534
+ const utf16 = [];
13535
+ for (let i = 0;i < text.length; ) {
13536
+ utf16.push(i);
13537
+ i += text.codePointAt(i) > 65535 ? 2 : 1;
13538
+ }
13539
+ utf16.push(text.length);
13540
+ const rangeOf = (token) => {
13541
+ const from = utf16[token.startIndex];
13542
+ const to = utf16[token.stopIndex + 1];
13543
+ return from === undefined || to === undefined || to < from ? undefined : [from, to];
13544
+ };
13545
+ const prose = [];
13546
+ let block;
13547
+ for (const token of tokenStream.getTokens()) {
13548
+ const name = vocabulary.getSymbolicName(token.type);
13549
+ const range = rangeOf(token);
13550
+ if (name === "BLOCK_ANNOTATION_TEXT" || name === "BLOCK_ANNOTATION_END") {
13551
+ if (!block || !range || token.startIndex !== block.next)
13552
+ return;
13553
+ if (name === "BLOCK_ANNOTATION_END") {
13554
+ block = undefined;
13555
+ } else {
13556
+ if (block.prose)
13557
+ prose.push(range);
13558
+ block.next = token.stopIndex + 1;
13559
+ }
13560
+ continue;
13561
+ }
13562
+ if (block)
13563
+ return;
13564
+ if (name === "BLOCK_ANNOTATION_BEGIN" || name === "DOC_BLOCK_ANNOTATION_BEGIN") {
13565
+ if (!range)
13566
+ return;
13567
+ block = {
13568
+ prose: PROSE_ROUTES.has(noteRoute(text.slice(...range)) ?? ""),
13569
+ next: token.stopIndex + 1
13570
+ };
13571
+ } else if (name === "ANNOTATION" || name === "DOC_ANNOTATION") {
13572
+ if (!range)
13573
+ return;
13574
+ if (PROSE_ROUTES.has(noteRoute(text.slice(...range)) ?? ""))
13575
+ prose.push([range[0] + 1, range[1]]);
13576
+ }
13577
+ }
13578
+ return block ? undefined : prose;
13579
+ } catch {
13580
+ return;
13581
+ }
13582
+ }
13583
+ function hasCallerAuthorizeAnnotation(callerText, precedingText = "") {
13584
+ if (!AUTHORIZE_ANNOTATION_ANYWHERE.test(callerText))
13585
+ return false;
13586
+ const text = precedingText + callerText;
13587
+ const bodies = proseRanges(text);
13588
+ lastRefusalKind = bodies !== undefined ? "lexed" : lexerMissing ? "no_lexer" : "whole_text";
13589
+ if (bodies === undefined)
13590
+ return true;
13591
+ const sticky = new RegExp(AUTHORIZE_TAG_LIKE, "iuy");
13592
+ const callerStart = precedingText.length;
13593
+ const from = text.lastIndexOf(`
13594
+ `, callerStart - 1) + 1;
13595
+ let body = 0;
13596
+ for (let at = text.indexOf("#", from);at !== -1; at = text.indexOf("#", at + 1)) {
13597
+ sticky.lastIndex = at;
13598
+ const hit = sticky.exec(text);
13599
+ if (!hit)
13600
+ continue;
13601
+ const end = at + hit[0].length;
13602
+ if (end <= callerStart)
13603
+ continue;
13604
+ while (body < bodies.length && bodies[body][1] <= at)
13605
+ body++;
13606
+ const [start, stop] = bodies[body] ?? [Infinity, -Infinity];
13607
+ if (!(start <= at && end <= stop))
13608
+ return true;
13609
+ }
13610
+ return false;
13611
+ }
13612
+ function assertNoCallerAuthorizeAnnotation(callerText, precedingText = "") {
13613
+ if (hasCallerAuthorizeAnnotation(callerText, precedingText))
13614
+ refuseCallerAuthorizeAnnotation();
13615
+ }
13616
+ function assertNoAuthorizeTagLike(callerText) {
13473
13617
  if (!AUTHORIZE_ANNOTATION_ANYWHERE.test(callerText))
13474
13618
  return;
13619
+ lastRefusalKind = "whole_text";
13620
+ refuseCallerAuthorizeAnnotation();
13621
+ }
13622
+ function refuseCallerAuthorizeAnnotation() {
13475
13623
  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
13624
  }
13477
13625
  function containsAuthorizeAnnotationTag(texts) {
@@ -13669,13 +13817,19 @@ ${rewrites}
13669
13817
  ` + `Test that the rewrite gates the same rows and check the row count ` + `matches what the string form served.`
13670
13818
  });
13671
13819
  }
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;
13820
+ 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
13821
  var init_authorize = __esm(() => {
13674
13822
  init_errors();
13675
13823
  init_authorize_routes();
13676
- AUTHORIZE_TAG_LIKE = String.raw`##?\|?[ \t]*[([{<]?[ \t]*(?:(?:(?:row|source)[-_]?)?authorize|access[-_]?filter)(?=[)\]}>]|[ \t]|$)`;
13824
+ AUTHORIZE_TAG_LIKE = String.raw`##?\|?[ \t]*(?:[([{<][ \t]*)?(?:(?:(?:row|source)[-_]?)?authorize|access[-_]?filter)(?=[)\]}>]|[ \t]|$)`;
13677
13825
  AUTHORIZE_ANNOTATION_ANYWHERE = new RegExp(AUTHORIZE_TAG_LIKE, "iu");
13678
13826
  REGEXP_SPECIAL = /[.*+?^${}()|[\]\\]/g;
13827
+ PROSE_ROUTES = new Set(["markdown", "text"]);
13828
+ parserLoader = loadMalloyParser;
13829
+ lastLexed = {
13830
+ text: "",
13831
+ prose: []
13832
+ };
13679
13833
  GIVEN_REF_PATTERN = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
13680
13834
  SINGLE_QUOTE_ESCAPE = /\\(['\\])/g;
13681
13835
  DOUBLE_QUOTE_ESCAPE = /\\(["\\])/g;
@@ -13688,9 +13842,12 @@ var init_authorize = __esm(() => {
13688
13842
  // src/authorize_metrics.ts
13689
13843
  function recordAuthorizeGuardRejection(field) {
13690
13844
  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')."
13845
+ 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)."
13846
+ });
13847
+ guardRejectionCounter.add(1, {
13848
+ field,
13849
+ match: lastCallerGuardRefusalKind()
13692
13850
  });
13693
- guardRejectionCounter.add(1, { field });
13694
13851
  }
13695
13852
  function recordAuthorizeBypass(entryPoint) {
13696
13853
  bypassCounter ??= publisherMeter().createCounter("publisher_authorize_bypass_total", {
@@ -14817,6 +14974,44 @@ var init_gate_classification = __esm(() => {
14817
14974
  init_gate_registry_walk();
14818
14975
  });
14819
14976
 
14977
+ // src/service/model_info.ts
14978
+ import { modelDefToModelInfo } from "@malloydata/malloy";
14979
+ function modelInfoOf(modelDef) {
14980
+ try {
14981
+ return modelDefToModelInfo(modelDef);
14982
+ } catch (err) {
14983
+ if (!isUnboundGiven(err) || !modelDef.givens)
14984
+ throw err;
14985
+ try {
14986
+ return modelDefToModelInfo({
14987
+ ...modelDef,
14988
+ givens: withPlaceholderDefaults(modelDef.givens)
14989
+ });
14990
+ } catch {
14991
+ throw err;
14992
+ }
14993
+ }
14994
+ }
14995
+ function isUnboundGiven(err) {
14996
+ return err?.code === "compiler-given-no-value";
14997
+ }
14998
+ function withPlaceholderDefaults(givens) {
14999
+ const out = {};
15000
+ for (const [id, given] of Object.entries(givens)) {
15001
+ if (given.default !== undefined) {
15002
+ out[id] = given;
15003
+ continue;
15004
+ }
15005
+ const isFilter = given.type.type === "filter expression";
15006
+ out[id] = {
15007
+ ...given,
15008
+ default: isFilter ? { node: "filterLiteral", filterSrc: "" } : { node: "null" }
15009
+ };
15010
+ }
15011
+ return out;
15012
+ }
15013
+ var init_model_info = () => {};
15014
+
14820
15015
  // src/service/query_text.ts
14821
15016
  import { ParseUtil } from "@malloydata/malloy-tag";
14822
15017
  function extractRunTargetSourceName(query) {
@@ -15748,6 +15943,17 @@ function isNotebookModelPath(modelPath) {
15748
15943
  const segments = modelPath.split("/");
15749
15944
  return segments.length === 2 && segments[0] === NOTEBOOKS_DIR;
15750
15945
  }
15946
+ function isDocumentModelPath(modelPath) {
15947
+ if (!modelPath.endsWith(MODEL_FILE_SUFFIX))
15948
+ return false;
15949
+ const segments = modelPath.split("/");
15950
+ return segments.length === 2 && (segments[0] === NOTEBOOKS_DIR || segments[0] === DASHBOARDS_DIR);
15951
+ }
15952
+ function documentKind(modelPath, tagKind) {
15953
+ if (tagKind === "notebook" || tagKind === "dashboard")
15954
+ return tagKind;
15955
+ return isNotebookModelPath(modelPath) ? "notebook" : "dashboard";
15956
+ }
15751
15957
  function routeOfNote(text) {
15752
15958
  return routeOf3({ value: text.trimStart() });
15753
15959
  }
@@ -15775,34 +15981,104 @@ function dashboardDescriptionNotes(notes) {
15775
15981
  return notes.filter((n) => line !== undefined && (n.at?.range.start.line ?? 0) > line).map((n) => n.text);
15776
15982
  }
15777
15983
  function hasArtifactLineOutsideBlocks(source, artifactLine) {
15984
+ return findArtifactLineOutsideBlocks(source, artifactLine) !== undefined;
15985
+ }
15986
+ function blockCloserFinder(lines) {
15987
+ const byKey = new Map;
15988
+ lines.forEach((line, at) => {
15989
+ const indent = /^[ \t]*/.exec(line)?.[0].length ?? 0;
15990
+ for (const closer of ["|##", "|#"])
15991
+ if (closesBlock(line, 0, indent, closer)) {
15992
+ const key = `${indent}${closer}`;
15993
+ const ats = byKey.get(key);
15994
+ if (ats)
15995
+ ats.push(at);
15996
+ else
15997
+ byKey.set(key, [at]);
15998
+ break;
15999
+ }
16000
+ });
16001
+ const cursor = new Map;
16002
+ return (column, closer, after) => {
16003
+ const key = `${column}${closer}`;
16004
+ const ats = byKey.get(key) ?? [];
16005
+ let k = cursor.get(key) ?? 0;
16006
+ while (k < ats.length && ats[k] <= after)
16007
+ k++;
16008
+ cursor.set(key, k);
16009
+ return ats[k] ?? -1;
16010
+ };
16011
+ }
16012
+ function findArtifactLineOutsideBlocks(source, artifactLine) {
15778
16013
  const lines = source.split(/\r\n|\r|\n/);
16014
+ const closerAfter = blockCloserFinder(lines);
15779
16015
  for (let i = 0;i < lines.length; i++) {
15780
16016
  const trimmed = lines[i].trimStart();
15781
16017
  const opener = /^(#{1,2})\|/.exec(trimmed);
15782
16018
  if (opener) {
15783
- const wanted = `|${opener[1]}`;
15784
- if (trimmed.includes(wanted, opener[0].length))
15785
- continue;
15786
16019
  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
- }
16020
+ const end = closerAfter(column, `|${opener[1]}`, i);
15793
16021
  if (end !== -1) {
16022
+ const block = [trimmed, ...lines.slice(i + 1, end)].join(`
16023
+ `);
16024
+ if (artifactLine.test(block))
16025
+ return block;
15794
16026
  i = end;
15795
16027
  continue;
15796
16028
  }
15797
16029
  }
15798
16030
  if (artifactLine.test(trimmed))
15799
- return true;
16031
+ return trimmed;
15800
16032
  }
15801
- return false;
16033
+ return lexedArtifactNote(source, lines, artifactLine);
16034
+ }
16035
+ function lexedArtifactNote(source, lines, artifactLine) {
16036
+ if (!lines.some((line) => /\S.*##/.test(line) && !/^\s*##/.test(line))) {
16037
+ return;
16038
+ }
16039
+ const translator = new MalloyTranslator(NOTEBOOK_PARSE_URL, null, {
16040
+ urls: { [NOTEBOOK_PARSE_URL]: source }
16041
+ });
16042
+ const parse = translator.parseStep.step(translator).parse;
16043
+ const stream = parse?.tokenStream;
16044
+ const tokens = stream?.getTokens?.() ?? [];
16045
+ const name = (type) => stream?.tokenSource?.vocabulary?.getSymbolicName(type);
16046
+ const text = (i) => source.slice(tokens[i].startIndex, tokens[i].stopIndex + 1);
16047
+ for (let i = 0;i < tokens.length; i++) {
16048
+ const kind = name(tokens[i].type);
16049
+ if (kind === "DOC_ANNOTATION" && artifactLine.test(text(i))) {
16050
+ return text(i).trim();
16051
+ }
16052
+ if (kind !== "DOC_BLOCK_ANNOTATION_BEGIN")
16053
+ continue;
16054
+ let body = "";
16055
+ let j = i;
16056
+ while (name(tokens[j].type) !== "BLOCK_ANNOTATION_END") {
16057
+ body += text(j);
16058
+ if (++j >= tokens.length || name(tokens[j].type) === "EOF") {
16059
+ return;
16060
+ }
16061
+ }
16062
+ if (artifactLine.test(body))
16063
+ return body;
16064
+ i = j;
16065
+ }
16066
+ return;
16067
+ }
16068
+ function artifactKindOfNotes(notes) {
16069
+ const tag = motlyTag(notes.map((note) => note.text).filter(isArtifactNoteText));
16070
+ return tagText(tag?.tag("artifact"), "kind");
15802
16071
  }
15803
16072
  function claimsToBeANotebook(source) {
15804
16073
  return hasArtifactLineOutsideBlocks(source, ARTIFACT_NOTE);
15805
16074
  }
16075
+ function artifactTagText(source) {
16076
+ return findArtifactLineOutsideBlocks(source, ARTIFACT_NOTE);
16077
+ }
16078
+ function artifactKindInText(source) {
16079
+ const line = artifactTagText(source);
16080
+ return line === undefined ? undefined : tagText(motlyTag([line])?.tag("artifact"), "kind");
16081
+ }
15806
16082
  function parseNotebookText(text) {
15807
16083
  try {
15808
16084
  const { problems, parse } = translateToParse(text);
@@ -16062,7 +16338,9 @@ ${body}`;
16062
16338
  codeLine: (codeLineOf(span.startCp, span.stopCp) ?? span.startLine) - span.startLine,
16063
16339
  ...caption !== undefined && { caption },
16064
16340
  startLine: span.startLine,
16065
- endLine: span.endLine
16341
+ endLine: span.endLine,
16342
+ start: span.start,
16343
+ end: span.end
16066
16344
  });
16067
16345
  continue;
16068
16346
  }
@@ -16116,7 +16394,7 @@ ${body}`;
16116
16394
  });
16117
16395
  }
16118
16396
  const runCount = items.filter((item) => item.kind === "statement" && item.run).length;
16119
- if (runCount !== modelDef.queryList.length) {
16397
+ if (modelDef && runCount !== modelDef.queryList.length) {
16120
16398
  return refuse({
16121
16399
  line: 1,
16122
16400
  message: `Line 1: the notebook reader found ${runCount} run: statements where Malloy compiled ${modelDef.queryList.length}, so its query cells cannot be matched to their results. Fix: none in the file; report it, since the reader and the compiler disagree.`
@@ -16131,7 +16409,7 @@ ${body}`;
16131
16409
  if (item.kind === "statement") {
16132
16410
  lineRun = undefined;
16133
16411
  const queryIndex = item.run ? runsSeen++ : undefined;
16134
- cells.push(item.run && belowTag ? {
16412
+ const pushed = item.run && belowTag ? {
16135
16413
  kind: "query",
16136
16414
  type: "code",
16137
16415
  text: item.text,
@@ -16160,7 +16438,9 @@ ${body}`;
16160
16438
  },
16161
16439
  startLine: item.startLine,
16162
16440
  endLine: item.endLine
16163
- });
16441
+ };
16442
+ cellOffsets.set(pushed, [item.start, item.end]);
16443
+ cells.push(pushed);
16164
16444
  return;
16165
16445
  }
16166
16446
  const { note } = item;
@@ -16239,9 +16519,12 @@ function readMarkdownBlocks(parse, text) {
16239
16519
  return blocks;
16240
16520
  }
16241
16521
  function notebookReaderProblem(modelPath, text, modelDef, url) {
16242
- if (!isNotebookModelPath(modelPath))
16522
+ if (!isDocumentModelPath(modelPath))
16243
16523
  return;
16244
- if (artifactNoteLine(ownModelNoteObjects(modelDef)) === undefined)
16524
+ const notes = ownModelNoteObjects(modelDef);
16525
+ if (artifactNoteLine(notes) === undefined)
16526
+ return;
16527
+ if (documentKind(modelPath, artifactKindOfNotes(notes)) !== "notebook")
16245
16528
  return;
16246
16529
  const parse = parseNotebookText(text);
16247
16530
  const error = isNotebookReaderError(parse) ? parse : readNotebookCells(parse, modelDef, text).error;
@@ -16261,7 +16544,7 @@ function notebookReaderProblem(modelPath, text, modelDef, url) {
16261
16544
  }
16262
16545
  };
16263
16546
  }
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) => {
16547
+ 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.", cellOffsets, isRuleNode = (node) => node !== undefined && node.ruleIndex !== undefined && (node.childCount ?? 0) > 0, callAccessor = (node, name) => {
16265
16548
  const accessor = node[name];
16266
16549
  return typeof accessor === "function" ? accessor.call(node) : undefined;
16267
16550
  }, STATEMENT_ACCESSORS, NOTEBOOK_PARSE_URL = "file:///publisher-notebook-reader/notebook.malloy", NOTE_TOKENS, TEXT_BLOCK_NAME;
@@ -16270,7 +16553,63 @@ var init_notebook = __esm(() => {
16270
16553
  init_annotations();
16271
16554
  init_query_text();
16272
16555
  init_motly();
16273
- ARTIFACT_NOTE = /^##[ \t]*artifact\b/;
16556
+ ARTIFACT_NOTE = {
16557
+ test(note) {
16558
+ const prefix = /^##\|?(?:[ \t\r\n]|$)/.exec(note);
16559
+ if (!prefix)
16560
+ return false;
16561
+ const bare = /[0-9A-Za-z_\u00C0-\u024F\u1E00-\u1EFF]+/y;
16562
+ let depth = 0;
16563
+ let before = "";
16564
+ for (let i = prefix[0].length;i < note.length; i++) {
16565
+ const c = note[i];
16566
+ if (/\s/.test(c))
16567
+ continue;
16568
+ if (note.startsWith("-...", i)) {
16569
+ i += 3;
16570
+ before = "";
16571
+ continue;
16572
+ }
16573
+ bare.lastIndex = i;
16574
+ const word = bare.exec(note)?.[0];
16575
+ if (c === '"' || c === "'") {
16576
+ const fence = note.startsWith(c.repeat(3), i) ? c.repeat(3) : c;
16577
+ i += fence.length;
16578
+ while (i < note.length && !note.startsWith(fence, i))
16579
+ i += note[i] === "\\" ? 2 : 1;
16580
+ i += fence.length - 1;
16581
+ } else if (c === "`") {
16582
+ let end = i + 1;
16583
+ while (end < note.length && note[end] !== "`" && note[end] !== `
16584
+ `)
16585
+ end += note[end] === "\\" ? 2 : 1;
16586
+ const name = note.slice(i + 1, end);
16587
+ if (depth === 0 && name === "artifact" && !/[=.-]/.test(before))
16588
+ return true;
16589
+ i = end;
16590
+ } else if (c === "#") {
16591
+ const eol = note.indexOf(`
16592
+ `, i);
16593
+ if (eol < 0)
16594
+ return false;
16595
+ i = eol;
16596
+ continue;
16597
+ } else if (c === "{" || c === "[")
16598
+ depth++;
16599
+ else if (c === "}" || c === "]")
16600
+ depth--;
16601
+ else if (word) {
16602
+ if (depth === 0 && word === "artifact" && !/[=.-]/.test(before))
16603
+ return true;
16604
+ i += word.length - 1;
16605
+ }
16606
+ before = c;
16607
+ }
16608
+ return false;
16609
+ }
16610
+ };
16611
+ ANY_ARTIFACT_NOTE = /^#{1,2}\|?(?=[ \t\r\n])(?:[^"'\\]|\\.|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')*?(?<![\w\u00C0-\u024F\u1E00-\u1EFF.$-])artifact(?![\w\u00C0-\u024F\u1E00-\u1EFF])/;
16612
+ cellOffsets = new WeakMap;
16274
16613
  STATEMENT_ACCESSORS = [
16275
16614
  ["runStatement", "run"],
16276
16615
  ["docAnnotations", "notes"],
@@ -22975,6 +23314,7 @@ function malloyGivenToApi(given) {
22975
23314
  type: renderedType,
22976
23315
  annotations: allNotes.filter((note) => !isReservedRoute(note.route) && note.route !== MARKDOWN_ROUTE).map((note) => note.text),
22977
23316
  ...readGivenControlSpec(allNotes.map((note) => note.text)),
23317
+ ...allNotes.some((note) => note.route === "secure") && { secure: true },
22978
23318
  default: given._internal?.defaultText
22979
23319
  };
22980
23320
  }
@@ -23088,9 +23428,12 @@ var init_given = __esm(() => {
23088
23428
  });
23089
23429
 
23090
23430
  // src/service/dashboard.ts
23091
- import { isSourceDef as isSourceDef5 } from "@malloydata/malloy";
23431
+ import { isJoined as isJoined3, isSourceDef as isSourceDef5 } from "@malloydata/malloy";
23432
+ function queryTiles(tiles) {
23433
+ return (tiles ?? []).filter((tile) => tile.kind === "query");
23434
+ }
23092
23435
  function filterPublisherOwnedRenderLogs(logs, modelPath) {
23093
- const couldBeATile = isDashboardModelPath(modelPath);
23436
+ const couldBeATile = isDocumentModelPath(modelPath);
23094
23437
  return logs.filter((log) => {
23095
23438
  const match = UNKNOWN_RENDER_TAG.exec(log.message ?? "");
23096
23439
  if (!match)
@@ -23115,6 +23458,24 @@ function dashboardSlug(modelPath) {
23115
23458
  function matchesDocumentedDashboardName(slug) {
23116
23459
  return /^[a-zA-Z0-9_-]+$/.test(slug);
23117
23460
  }
23461
+ function readTileEntries(artifact) {
23462
+ return artifact.array("tiles")?.flatMap((entry) => {
23463
+ const text = tagText(entry);
23464
+ if (text === undefined)
23465
+ return [];
23466
+ if (tagText(entry, "kind") !== "text")
23467
+ return [{ kind: "query", expression: text }];
23468
+ const colspan = positiveInteger(tagNumeric(entry, "colspan"));
23469
+ return [
23470
+ {
23471
+ kind: "text",
23472
+ name: text,
23473
+ ...colspan !== undefined && { colspan },
23474
+ ...entry.has("break") && { break: true }
23475
+ }
23476
+ ];
23477
+ });
23478
+ }
23118
23479
  function readArtifactTag(tag, declaredType) {
23119
23480
  const artifact = tag.tag("artifact");
23120
23481
  if (!artifact)
@@ -23123,12 +23484,32 @@ function readArtifactTag(tag, declaredType) {
23123
23484
  const autorun = readAutorun(artifact);
23124
23485
  return {
23125
23486
  title: tagText(artifact, "title"),
23126
- tiles: artifact.array("tiles")?.filter((tile) => tagText(tile, "kind") !== "text").map((tile) => tagText(tile)).filter((tile) => tile !== undefined),
23487
+ kind: tagText(artifact, "kind"),
23488
+ tiles: readTileEntries(artifact),
23127
23489
  dashboardColumns: tag.tag("dashboard")?.has("columns") ? positiveInteger(tagNumeric(tag.tag("dashboard"), "columns")) : artifact.array("tiles") ? positiveInteger(tagNumeric(artifact, "dashboard_columns")) : undefined,
23128
23490
  givens,
23129
23491
  autorun
23130
23492
  };
23131
23493
  }
23494
+ function readDocumentTileExpressions(notes) {
23495
+ const tag = motlyTag(notes);
23496
+ const composite = tag ? readArtifactTag(tag, () => {
23497
+ return;
23498
+ }) : undefined;
23499
+ return (composite?.tiles ?? []).flatMap((entry) => entry.kind === "text" ? [] : [entry.expression]);
23500
+ }
23501
+ function readTextBlocks(notes) {
23502
+ return notes.flatMap((note) => {
23503
+ const newline = note.indexOf(`
23504
+ `);
23505
+ const opener = parseMarkdownOpener(newline === -1 ? note : note.slice(0, newline));
23506
+ if (opener?.level !== 2 || !opener.name)
23507
+ return [];
23508
+ const body = newline === -1 ? "" : note.slice(newline + 1);
23509
+ return [{ name: opener.name, body: body.replace(/\r\n?/g, `
23510
+ `) }];
23511
+ });
23512
+ }
23132
23513
  function positiveInteger(value) {
23133
23514
  return value !== undefined && Number.isInteger(value) && value >= 1 ? value : undefined;
23134
23515
  }
@@ -23176,6 +23557,11 @@ function readDashboardModelFacts(modelPath, modelDef, surfacedGivenNames, gatesB
23176
23557
  for (const field of obj.fields) {
23177
23558
  const fieldName = field.as || field.name;
23178
23559
  fieldNames.add(fieldName);
23560
+ if (isJoined3(field)) {
23561
+ for (const joined of field.fields) {
23562
+ fieldNames.add(`${fieldName}.${joined.as || joined.name}`);
23563
+ }
23564
+ }
23179
23565
  if (field.type === "turtle") {
23180
23566
  const refs = new Set(sourceGivens);
23181
23567
  collectGivenRefs(field.pipeline, refs);
@@ -23246,7 +23632,11 @@ function tagTextArray(tag, ...path) {
23246
23632
  return items?.map((item) => tagText(item)).filter(isString);
23247
23633
  }
23248
23634
  function resolveTileGivens(tile, facts) {
23249
- return referencedTileGivens(tile, facts)?.filter((name) => facts.givens.has(name));
23635
+ const reads = referencedTileGivens(tile, facts);
23636
+ if (reads === undefined)
23637
+ return;
23638
+ const gateReads = facts.compiledTileGivens?.get(normalizeTileExpression(tile))?.gateReads ?? [];
23639
+ return Array.from(new Set([...reads, ...gateReads])).filter((name) => facts.givens.has(name));
23250
23640
  }
23251
23641
  function resolveTileLayout(tile, facts) {
23252
23642
  const annotations = tileAnnotations(tile, facts);
@@ -23258,7 +23648,15 @@ function tileAnnotations(tile, facts) {
23258
23648
  }
23259
23649
  function referencedTileGivens(tile, facts) {
23260
23650
  const normalized = normalizeTileExpression(tile);
23261
- return facts.viewGivens.get(normalized) ?? facts.queries.find((query) => query.name === normalized)?.givens;
23651
+ const statically = facts.viewGivens.get(normalized) ?? facts.queries.find((query) => query.name === normalized)?.givens;
23652
+ const compiled = facts.compiledTileGivens?.get(normalized)?.reads;
23653
+ if (!compiled)
23654
+ return statically;
23655
+ const rank = (name) => {
23656
+ const at = statically?.indexOf(name) ?? -1;
23657
+ return at === -1 ? Number.MAX_SAFE_INTEGER : at;
23658
+ };
23659
+ return [...compiled].sort((a, b) => rank(a) - rank(b));
23262
23660
  }
23263
23661
  function buildGivenSpecs(names, facts) {
23264
23662
  const declarations = facts.givens;
@@ -23283,6 +23681,9 @@ function givenSpec(declaration, facts) {
23283
23681
  type: declaration.type,
23284
23682
  default: declaration.type.startsWith("filter<") && declaration.default !== undefined ? unwrapFilterLiteral(declaration.default) : declaration.default,
23285
23683
  annotations: declaration.annotations.filter((text) => /^##?\(/.test(text) && !isMarkdownNote(text)),
23684
+ ...declaration.annotations.some((text) => /^#\(secure\)/.test(text)) && {
23685
+ secure: true
23686
+ },
23286
23687
  ...control
23287
23688
  };
23288
23689
  }
@@ -23291,7 +23692,7 @@ function factsCarryArtifactTag(facts) {
23291
23692
  return true;
23292
23693
  return facts.queries.some((query) => Boolean(motlyTag(query.annotations)?.tag("artifact")));
23293
23694
  }
23294
- function buildDashboardManifest(facts) {
23695
+ function buildDashboardManifest(facts, options) {
23295
23696
  const name = dashboardSlug(facts.modelPath);
23296
23697
  const base = {
23297
23698
  name,
@@ -23300,26 +23701,44 @@ function buildDashboardManifest(facts) {
23300
23701
  const modelTag = motlyTag(facts.modelAnnotations);
23301
23702
  const declaredType = (name2) => facts.givens.get(name2)?.type;
23302
23703
  const composite = modelTag ? readArtifactTag(modelTag, declaredType) : undefined;
23303
- if (composite?.tiles?.length) {
23304
- const tiles = composite.tiles.map((query) => {
23704
+ const kind = documentKind(facts.modelPath, composite?.kind);
23705
+ if (composite?.tiles && (composite.tiles.length > 0 || kind === "notebook")) {
23706
+ const blocks = readTextBlocks(facts.modelAnnotations);
23707
+ const tiles = composite.tiles.map((entry) => {
23708
+ if (entry.kind === "text") {
23709
+ return {
23710
+ kind: "text",
23711
+ name: entry.name,
23712
+ markdown: blocks.find((block) => block.name === entry.name)?.body ?? "",
23713
+ ...entry.colspan !== undefined && { colspan: entry.colspan },
23714
+ ...entry.break && { break: true }
23715
+ };
23716
+ }
23717
+ const query = entry.expression;
23718
+ if (options?.restrictedTiles?.has(normalizeTileExpression(query))) {
23719
+ return { kind: "query", query, restricted: true };
23720
+ }
23305
23721
  const givenNames = resolveTileGivens(query, facts);
23306
23722
  const layout = resolveTileLayout(query, facts);
23307
23723
  return {
23724
+ kind: "query",
23308
23725
  query,
23309
23726
  ...givenNames ? { givenNames } : {},
23310
23727
  ...layout
23311
23728
  };
23312
23729
  });
23730
+ const queries = queryTiles(tiles).filter((tile) => !tile.restricted);
23313
23731
  const doc = docCommentTitleAndDescription(facts.descriptionNotes ?? facts.modelAnnotations, composite.title);
23314
23732
  return {
23315
23733
  ...base,
23734
+ kind,
23316
23735
  title: doc.title ?? name,
23317
23736
  description: doc.description,
23318
23737
  tiles,
23319
- dashboardColumns: composite.dashboardColumns,
23738
+ dashboardColumns: kind === "notebook" ? 1 : composite.dashboardColumns,
23320
23739
  startingGivens: composite.givens,
23321
23740
  autorun: composite.autorun,
23322
- givens: buildGivenSpecs(tiles.some((tile) => tile.givenNames === undefined) ? Array.from(facts.givens.keys()) : tiles.flatMap((tile) => tile.givenNames ?? []), facts)
23741
+ 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
23742
  };
23324
23743
  }
23325
23744
  for (const query of facts.queries) {
@@ -23330,6 +23749,7 @@ function buildDashboardManifest(facts) {
23330
23749
  const doc = docCommentTitleAndDescription(query.annotations, artifact.title);
23331
23750
  return {
23332
23751
  ...base,
23752
+ kind: "dashboard",
23333
23753
  title: doc.title ?? name,
23334
23754
  description: doc.description,
23335
23755
  query: query.name,
@@ -23371,19 +23791,33 @@ function lintDashboard(facts, manifest) {
23371
23791
  continue;
23372
23792
  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
23793
  }
23794
+ const textBlocks = readTextBlocks(facts.modelAnnotations);
23374
23795
  for (const entry of artifactTag?.array("tiles") ?? []) {
23375
23796
  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.`);
23797
+ const label = tagText(entry) ?? "a tile";
23798
+ const read = kind === "text" ? ["kind", "colspan", "break"] : [];
23799
+ const carried = Object.keys(entry.dict ?? {}).filter((property) => !read.includes(property) && !(property === "kind" && kind === "query"));
23800
+ if (carried.length > 0) {
23801
+ const named = carried.map((property) => `\`${property}\``).join(", ");
23802
+ 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\`).`);
23803
+ }
23804
+ if (kind !== "text")
23378
23805
  continue;
23806
+ if (!textBlocks.some((block) => block.name === label)) {
23807
+ 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.`);
23379
23808
  }
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\`).`);
23809
+ if (entry.has("colspan") && positiveInteger(tagNumeric(entry, "colspan")) === undefined) {
23810
+ add(`colspan on text tile \`${label}\` must be a positive integer. The tile spans one column.`);
23811
+ }
23812
+ }
23813
+ const seenBlocks = new Set;
23814
+ for (const { name } of textBlocks) {
23815
+ if (seenBlocks.has(name)) {
23816
+ add(`the \`##|(markdown) ${name}\` block is written twice, and only the first is shown. Fix: rename or delete the second.`);
23817
+ }
23818
+ seenBlocks.add(name);
23385
23819
  }
23386
- for (const { query: tile } of manifest.tiles ?? []) {
23820
+ for (const { query: tile } of manifest.kind === "notebook" ? [] : queryTiles(manifest.tiles)) {
23387
23821
  const tag = motlyTag(tileAnnotations(tile, facts) ?? []);
23388
23822
  if (!tag?.has("colspan"))
23389
23823
  continue;
@@ -23402,21 +23836,28 @@ function lintDashboard(facts, manifest) {
23402
23836
  }
23403
23837
  const columnsTag = ownTag?.tag("dashboard");
23404
23838
  if (columnsTag?.has("columns")) {
23405
- if (positiveInteger(tagNumeric(columnsTag, "columns")) === undefined) {
23839
+ const width = positiveInteger(tagNumeric(columnsTag, "columns"));
23840
+ if (width === undefined) {
23406
23841
  const raw = tagText(columnsTag, "columns");
23407
23842
  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.`);
23843
+ } else if (width > MAX_BUILDER_COLUMNS) {
23844
+ add(`# dashboard { columns=${width} } is wider than the builder offers (${MAX_BUILDER_COLUMNS}); the reader still renders it.`);
23408
23845
  }
23409
23846
  }
23410
- for (const { query: tile } of manifest.tiles ?? []) {
23847
+ for (const { query: tile } of queryTiles(manifest.tiles)) {
23411
23848
  const parts = plainTileParts(tile);
23412
23849
  if (!parts)
23413
23850
  continue;
23414
23851
  const resolved = referencedTileGivens(tile, facts) !== undefined || "name" in parts && facts.queries.some((query) => query.name === parts.name);
23852
+ if (!resolved && "name" in parts && textBlocks.some((block) => block.name === parts.name)) {
23853
+ 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");
23854
+ continue;
23855
+ }
23415
23856
  if (!resolved) {
23416
23857
  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
23858
  add(`tile "${tile}" does not resolve: ${detail}.`, "error");
23418
23859
  }
23419
- const unbindable = (referencedTileGivens(tile, facts) ?? []).filter((name) => !facts.givens.has(name));
23860
+ const unbindable = Array.from(new Set(referencedTileGivens(tile, facts) ?? [])).filter((name) => !facts.givens.has(name));
23420
23861
  for (const name of unbindable) {
23421
23862
  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
23863
  }
@@ -23579,9 +24020,40 @@ function lintSelfDrills(facts) {
23579
24020
  }
23580
24021
  return Array.from(findings.values());
23581
24022
  }
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", …].`;
24023
+ async function compileTileGivens(tiles, materializer, fallbackRegistry, gatesOf, skip, onPrepared, onCompileError) {
24024
+ const compiled = new Map;
24025
+ for (const tile of tiles) {
24026
+ const key = normalizeTileExpression(tile);
24027
+ if (compiled.has(key) || skip?.has(key))
24028
+ continue;
24029
+ let prepared;
24030
+ try {
24031
+ prepared = await materializer.loadQuery(`run: ${tile}`).getPreparedQuery();
24032
+ } catch (error) {
24033
+ onCompileError?.(tile, error);
24034
+ continue;
24035
+ }
24036
+ await onPrepared?.(tile, prepared);
24037
+ const usage = prepared._query?.givenUsage;
24038
+ if (!usage)
24039
+ continue;
24040
+ const registry = prepared._modelDef?.givens ?? fallbackRegistry ?? {};
24041
+ const reads = usage.map((given) => registry[given.id]?.name).filter((name) => name !== undefined);
24042
+ const target = prepared._query?.structRef;
24043
+ const sourceName = typeof target === "string" ? target : target?.as || target?.name;
24044
+ const gateReads = new Set;
24045
+ for (const expr of sourceName ? gatesOf(sourceName) ?? [] : []) {
24046
+ for (const name of referencedGivenNames(expr))
24047
+ gateReads.add(name);
24048
+ }
24049
+ compiled.set(key, { reads, gateReads: Array.from(gateReads) });
24050
+ }
24051
+ return compiled;
24052
+ }
24053
+ 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
24054
  var init_dashboard = __esm(() => {
23584
24055
  init_annotations();
24056
+ init_authorize();
23585
24057
  init_notebook();
23586
24058
  init_given();
23587
24059
  init_motly();
@@ -23800,9 +24272,8 @@ var init_filter = __esm(() => {
23800
24272
 
23801
24273
  // src/service/source_extraction.ts
23802
24274
  import {
23803
- isJoined as isJoined3,
23804
- isSourceDef as isSourceDef6,
23805
- modelDefToModelInfo
24275
+ isJoined as isJoined4,
24276
+ isSourceDef as isSourceDef6
23806
24277
  } from "@malloydata/malloy";
23807
24278
  function joinFieldNamesUnresolvableDeclaration(field, modelDef) {
23808
24279
  const ids = [field.referenceID, field.sourceID].filter((id) => !!id);
@@ -23833,7 +24304,7 @@ function considerNoteOwner(declaredBy, note, candidate) {
23833
24304
  function collectSourceInfos(modelDef) {
23834
24305
  const names = Object.keys(modelDef.contents).filter((name) => isSourceDef6(modelDef.contents[name]));
23835
24306
  const exports = [...new Set([...modelDef.exports, ...names])];
23836
- return modelDefToModelInfo({ ...modelDef, exports }).entries.filter((entry) => entry.kind === "source");
24307
+ return modelInfoOf({ ...modelDef, exports }).entries.filter((entry) => entry.kind === "source");
23837
24308
  }
23838
24309
  function extractSourcesFromModelDef(modelDef, givens, onParseError) {
23839
24310
  const filterMap = new Map;
@@ -23971,7 +24442,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
23971
24442
  continue;
23972
24443
  }
23973
24444
  const fieldName = field.as || field.name;
23974
- if (isJoined3(field) && isSourceDef6(field)) {
24445
+ if (isJoined4(field) && isSourceDef6(field)) {
23975
24446
  const joinedStruct = field;
23976
24447
  if (joinFieldNamesUnresolvableDeclaration(joinedStruct, modelDef)) {
23977
24448
  continue;
@@ -24029,16 +24500,17 @@ var init_source_extraction = __esm(() => {
24029
24500
  init_annotations();
24030
24501
  init_authorize();
24031
24502
  init_authorize_routes();
24503
+ init_model_info();
24032
24504
  init_filter();
24033
24505
  init_gate_registry_walk();
24034
24506
  });
24035
24507
 
24036
24508
  // src/utils.ts
24037
- import * as fs from "fs";
24038
- import * as path from "path";
24509
+ import * as fs2 from "fs";
24510
+ import * as path2 from "path";
24039
24511
  import { fileURLToPath } from "url";
24040
24512
  function ignoreDotfiles(file) {
24041
- return path.basename(file).startsWith(".");
24513
+ return path2.basename(file).startsWith(".");
24042
24514
  }
24043
24515
  function errMessage(err) {
24044
24516
  return err instanceof Error ? err.message : String(err);
@@ -24047,11 +24519,11 @@ var URL_READER;
24047
24519
  var init_utils = __esm(() => {
24048
24520
  URL_READER = {
24049
24521
  readURL: (url) => {
24050
- let path2 = url.toString();
24522
+ let path3 = url.toString();
24051
24523
  if (url.protocol == "file:") {
24052
- path2 = fileURLToPath(url);
24524
+ path3 = fileURLToPath(url);
24053
24525
  }
24054
- return fs.promises.readFile(path2, "utf8");
24526
+ return fs2.promises.readFile(path3, "utf8");
24055
24527
  }
24056
24528
  };
24057
24529
  });
@@ -24065,20 +24537,20 @@ init_error_wire();
24065
24537
  init_authorize();
24066
24538
  init_gate_classification();
24067
24539
  init_gate_dimension();
24540
+ init_model_info();
24068
24541
  var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
24069
24542
  import {
24070
24543
  contextOverlay,
24071
24544
  isSourceDef as isSourceDef7,
24072
24545
  MalloyConfig,
24073
- modelDefToModelInfo as modelDefToModelInfo2,
24074
24546
  Runtime
24075
24547
  } from "@malloydata/malloy";
24076
24548
  import {
24077
24549
  MalloySQLParser,
24078
24550
  MalloySQLStatementType
24079
24551
  } from "@malloydata/malloy-sql";
24080
- import * as fs2 from "fs";
24081
- import * as path2 from "path";
24552
+ import * as fs3 from "fs";
24553
+ import * as path3 from "path";
24082
24554
  import { parentPort, threadId } from "node:worker_threads";
24083
24555
  import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
24084
24556
 
@@ -24265,6 +24737,254 @@ var EXPLORES_EMPTY_SUPPRESSES_CONVENTION = `"explores" in publisher.json is depr
24265
24737
  var EXPLORES_EMPTY_IS_UNCURATED = `"explores": [] in publisher.json does nothing. Fix: delete it.`;
24266
24738
  var QUERYABLE_SOURCES_DEPRECATION = `"queryableSources" in publisher.json does nothing. Fix: delete it.`;
24267
24739
 
24740
+ // src/service/package_retrieval.ts
24741
+ init_errors();
24742
+ import { createHash } from "crypto";
24743
+ import * as fs from "fs";
24744
+ import * as path from "path";
24745
+ var PACKAGE_REPRESENTATIONS = ["single", "facets"];
24746
+ var KEYPHRASE_MODES = ["auto", "never", "always"];
24747
+ var REFINE_LEVEL_NAMES = ["LOW", "MEDIUM", "HIGH"];
24748
+ var DEFAULT_RERANK_TOP_SOURCES = 8;
24749
+ var DEFAULT_REFINE_MIN_LEVEL = "MEDIUM";
24750
+ var RETRIEVAL_KEYS = [
24751
+ "representation",
24752
+ "keyphrases",
24753
+ "refine",
24754
+ "rerank",
24755
+ "sourceMatch",
24756
+ "sourceSummary",
24757
+ "prompts"
24758
+ ];
24759
+ var PROMPT_KEYS = [
24760
+ "keyphrase",
24761
+ "refine",
24762
+ "rerank",
24763
+ "sourceMatch",
24764
+ "sourceSummary"
24765
+ ];
24766
+ var REFINE_KEYS = ["enabled", "minLevel"];
24767
+ var RERANK_KEYS = ["enabled", "topSources"];
24768
+ var SOURCE_MATCH_KEYS = ["enabled"];
24769
+ var SOURCE_SUMMARY_KEYS = ["enabled"];
24770
+ var DEFAULT_PACKAGE_RETRIEVAL = {
24771
+ representation: "single",
24772
+ keyphrases: "auto",
24773
+ prompts: {}
24774
+ };
24775
+ function refineSettingsOf(retrieval) {
24776
+ return retrieval.refine ?? {
24777
+ enabled: "auto",
24778
+ minLevel: DEFAULT_REFINE_MIN_LEVEL
24779
+ };
24780
+ }
24781
+ function rerankSettingsOf(retrieval) {
24782
+ return retrieval.rerank ?? {
24783
+ enabled: "auto",
24784
+ topSources: DEFAULT_RERANK_TOP_SOURCES
24785
+ };
24786
+ }
24787
+ function sourceMatchSettingsOf(retrieval) {
24788
+ return retrieval.sourceMatch ?? { enabled: "auto" };
24789
+ }
24790
+ function sourceSummarySettingsOf(retrieval) {
24791
+ return retrieval.sourceSummary ?? { enabled: "auto" };
24792
+ }
24793
+ function fail(message) {
24794
+ throw new PackageManifestError(message);
24795
+ }
24796
+ function describe(value) {
24797
+ return JSON.stringify(value) ?? "nothing";
24798
+ }
24799
+ function stepsOutside(within) {
24800
+ return within === ".." || within.startsWith(`..${path.sep}`) || path.isAbsolute(within);
24801
+ }
24802
+ function resolvePromptPath(packageRoot, rel, promptKey = "keyphrase") {
24803
+ const key = `retrieval.prompts.${promptKey}`;
24804
+ const fix = `Fix: use a path inside the package, e.g. "prompts/${promptKey}.md".`;
24805
+ if (rel.includes("\x00") || path.isAbsolute(rel) || /^[A-Za-z]:[\\/]/.test(rel) || rel.startsWith("\\")) {
24806
+ fail(`Invalid publisher.json ${key}: ${describe(rel)} is an absolute path. ${fix}`);
24807
+ }
24808
+ const root = path.resolve(packageRoot);
24809
+ const resolved = path.resolve(root, rel);
24810
+ const within = path.relative(root, resolved);
24811
+ if (within === "" || stepsOutside(within)) {
24812
+ fail(`Invalid publisher.json ${key}: ${describe(rel)} resolves outside the package directory. ${fix}`);
24813
+ }
24814
+ return resolved;
24815
+ }
24816
+ function objectBlock(raw, key, valid, fix) {
24817
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
24818
+ fail(`Invalid publisher.json ${key}: expected an object, got ${describe(raw)}. Fix: ${fix}`);
24819
+ }
24820
+ const obj = raw;
24821
+ const unknown = Object.keys(obj).filter((k) => !valid.includes(k));
24822
+ if (unknown.length > 0) {
24823
+ fail(`Invalid publisher.json ${key}: unknown key ${unknown.map((k) => `'${k}'`).join(", ")}. ` + `Valid keys: ${valid.join(", ")}.`);
24824
+ }
24825
+ return obj;
24826
+ }
24827
+ function parseEnabled(value, key) {
24828
+ if (value === undefined || value === null)
24829
+ return "auto";
24830
+ if (value === "auto" || value === true || value === false)
24831
+ return value;
24832
+ return fail(`Invalid publisher.json ${key}: expected "auto", true or false, got ${describe(value)}. ` + `Fix: "enabled": "auto" (on when the server has an LLM), true (require one) or false.`);
24833
+ }
24834
+ function parseRefine(raw) {
24835
+ const obj = objectBlock(raw, "retrieval.refine", REFINE_KEYS, `"refine": { "enabled": "auto", "minLevel": "MEDIUM" }`);
24836
+ const minLevel = obj.minLevel ?? DEFAULT_REFINE_MIN_LEVEL;
24837
+ if (typeof minLevel !== "string" || !REFINE_LEVEL_NAMES.includes(minLevel)) {
24838
+ fail(`Invalid publisher.json retrieval.refine.minLevel: expected one of ${REFINE_LEVEL_NAMES.join(", ")}, got ${describe(obj.minLevel)}. ` + `Fix: "minLevel": "MEDIUM" (drop candidates the model rates below it).`);
24839
+ }
24840
+ return {
24841
+ enabled: parseEnabled(obj.enabled, "retrieval.refine.enabled"),
24842
+ minLevel
24843
+ };
24844
+ }
24845
+ function parseRerank(raw) {
24846
+ const obj = objectBlock(raw, "retrieval.rerank", RERANK_KEYS, `"rerank": { "enabled": "auto", "topSources": ${DEFAULT_RERANK_TOP_SOURCES} }`);
24847
+ const top = obj.topSources ?? DEFAULT_RERANK_TOP_SOURCES;
24848
+ if (typeof top !== "number" || !Number.isSafeInteger(top) || top <= 0) {
24849
+ fail(`Invalid publisher.json retrieval.rerank.topSources: expected a positive integer, got ${describe(obj.topSources)}. ` + `Fix: "topSources": ${DEFAULT_RERANK_TOP_SOURCES}`);
24850
+ }
24851
+ return {
24852
+ enabled: parseEnabled(obj.enabled, "retrieval.rerank.enabled"),
24853
+ topSources: top
24854
+ };
24855
+ }
24856
+ function parseSourceMatch(raw) {
24857
+ const obj = objectBlock(raw, "retrieval.sourceMatch", SOURCE_MATCH_KEYS, `"sourceMatch": { "enabled": "auto" }`);
24858
+ return {
24859
+ enabled: parseEnabled(obj.enabled, "retrieval.sourceMatch.enabled")
24860
+ };
24861
+ }
24862
+ function parseSourceSummary(raw) {
24863
+ const obj = objectBlock(raw, "retrieval.sourceSummary", SOURCE_SUMMARY_KEYS, `"sourceSummary": { "enabled": "auto" }`);
24864
+ return {
24865
+ enabled: parseEnabled(obj.enabled, "retrieval.sourceSummary.enabled")
24866
+ };
24867
+ }
24868
+ function parsePackageRetrieval(raw) {
24869
+ if (raw === undefined || raw === null) {
24870
+ return {
24871
+ representation: DEFAULT_PACKAGE_RETRIEVAL.representation,
24872
+ keyphrases: DEFAULT_PACKAGE_RETRIEVAL.keyphrases,
24873
+ promptPaths: {}
24874
+ };
24875
+ }
24876
+ if (typeof raw !== "object" || Array.isArray(raw)) {
24877
+ fail(`Invalid publisher.json retrieval: expected an object, got ${describe(raw)}. ` + `Fix: "retrieval": { "representation": "single" }`);
24878
+ }
24879
+ const obj = raw;
24880
+ const unknown = Object.keys(obj).filter((k) => !RETRIEVAL_KEYS.includes(k));
24881
+ if (unknown.length > 0) {
24882
+ fail(`Invalid publisher.json retrieval: unknown key ${unknown.map((k) => `'${k}'`).join(", ")}. ` + `Valid keys: ${RETRIEVAL_KEYS.join(", ")}.`);
24883
+ }
24884
+ const representation = obj.representation ?? "single";
24885
+ if (typeof representation !== "string" || !PACKAGE_REPRESENTATIONS.includes(representation)) {
24886
+ fail(`Invalid publisher.json retrieval.representation: expected one of ${PACKAGE_REPRESENTATIONS.join(", ")}, got ${describe(obj.representation)}. ` + `Fix: "representation": "single" (one row per entity) or "facets" (a name row plus doc chunks).`);
24887
+ }
24888
+ const keyphrases = obj.keyphrases ?? "auto";
24889
+ if (typeof keyphrases !== "string" || !KEYPHRASE_MODES.includes(keyphrases)) {
24890
+ fail(`Invalid publisher.json retrieval.keyphrases: expected one of ${KEYPHRASE_MODES.join(", ")}, got ${describe(obj.keyphrases)}. ` + `Fix: "keyphrases": "auto".`);
24891
+ }
24892
+ const refine = obj.refine === undefined || obj.refine === null ? undefined : parseRefine(obj.refine);
24893
+ const rerank = obj.rerank === undefined || obj.rerank === null ? undefined : parseRerank(obj.rerank);
24894
+ const sourceMatch = obj.sourceMatch === undefined || obj.sourceMatch === null ? undefined : parseSourceMatch(obj.sourceMatch);
24895
+ const sourceSummary = obj.sourceSummary === undefined || obj.sourceSummary === null ? undefined : parseSourceSummary(obj.sourceSummary);
24896
+ const promptPaths = {};
24897
+ if (obj.prompts !== undefined && obj.prompts !== null) {
24898
+ if (typeof obj.prompts !== "object" || Array.isArray(obj.prompts)) {
24899
+ fail(`Invalid publisher.json retrieval.prompts: expected an object, got ${describe(obj.prompts)}. ` + `Fix: "prompts": { "keyphrase": "prompts/keyphrase.md" }`);
24900
+ }
24901
+ const prompts = obj.prompts;
24902
+ const badPrompt = Object.keys(prompts).filter((k) => !PROMPT_KEYS.includes(k));
24903
+ if (badPrompt.length > 0) {
24904
+ fail(`Invalid publisher.json retrieval.prompts: unknown key ${badPrompt.map((k) => `'${k}'`).join(", ")}. ` + `Valid keys: ${PROMPT_KEYS.join(", ")}.`);
24905
+ }
24906
+ for (const key of PROMPT_KEYS) {
24907
+ const value = prompts[key];
24908
+ if (value === undefined || value === null)
24909
+ continue;
24910
+ if (typeof value !== "string" || value.trim() === "") {
24911
+ fail(`Invalid publisher.json retrieval.prompts.${key}: expected a file path inside the package, got ${describe(value)}. ` + `Fix: "${key}": "prompts/${key}.md"`);
24912
+ }
24913
+ promptPaths[key] = value;
24914
+ }
24915
+ }
24916
+ return {
24917
+ representation,
24918
+ keyphrases,
24919
+ ...refine ? { refine } : {},
24920
+ ...rerank ? { rerank } : {},
24921
+ ...sourceMatch ? { sourceMatch } : {},
24922
+ ...sourceSummary ? { sourceSummary } : {},
24923
+ promptPaths
24924
+ };
24925
+ }
24926
+ async function readPromptFile(packageRoot, key, rel) {
24927
+ const file = resolvePromptPath(packageRoot, rel, key);
24928
+ const at = `retrieval.prompts.${key}`;
24929
+ let text;
24930
+ try {
24931
+ const [realRoot, realFile] = await Promise.all([
24932
+ fs.promises.realpath(packageRoot),
24933
+ fs.promises.realpath(file)
24934
+ ]);
24935
+ const within = path.relative(realRoot, realFile);
24936
+ if (stepsOutside(within)) {
24937
+ fail(`Invalid publisher.json ${at}: ${describe(rel)} resolves outside the package directory through a link. ` + `Fix: copy the file into the package.`);
24938
+ }
24939
+ text = await fs.promises.readFile(realFile, "utf8");
24940
+ } catch (error) {
24941
+ if (error instanceof PackageManifestError)
24942
+ throw error;
24943
+ return fail(`Invalid publisher.json ${at}: cannot read ${describe(rel)} (${error.message}). ` + `Fix: create the file inside the package or remove the key.`);
24944
+ }
24945
+ if (text.trim() === "") {
24946
+ fail(`Invalid publisher.json ${at}: ${describe(rel)} is empty. ` + `Fix: write the prompt, or remove the key to use the built-in one.`);
24947
+ }
24948
+ return {
24949
+ path: rel,
24950
+ text,
24951
+ hash: createHash("sha256").update(text).digest("hex")
24952
+ };
24953
+ }
24954
+ async function readPackageRetrieval(packageRoot, raw) {
24955
+ const parsed = parsePackageRetrieval(raw);
24956
+ const prompts = {};
24957
+ for (const key of PROMPT_KEYS) {
24958
+ const rel = parsed.promptPaths[key];
24959
+ if (rel !== undefined) {
24960
+ prompts[key] = await readPromptFile(packageRoot, key, rel);
24961
+ }
24962
+ }
24963
+ return {
24964
+ representation: parsed.representation,
24965
+ keyphrases: parsed.keyphrases,
24966
+ ...parsed.refine ? { refine: parsed.refine } : {},
24967
+ ...parsed.rerank ? { rerank: parsed.rerank } : {},
24968
+ ...parsed.sourceMatch ? { sourceMatch: parsed.sourceMatch } : {},
24969
+ ...parsed.sourceSummary ? { sourceSummary: parsed.sourceSummary } : {},
24970
+ prompts
24971
+ };
24972
+ }
24973
+ function assertRequiredStagesAvailable(retrieval, llmConfigured) {
24974
+ if (llmConfigured)
24975
+ return;
24976
+ for (const [key, stage] of [
24977
+ ["refine", retrieval.refine],
24978
+ ["rerank", retrieval.rerank],
24979
+ ["sourceMatch", retrieval.sourceMatch],
24980
+ ["sourceSummary", retrieval.sourceSummary]
24981
+ ]) {
24982
+ if (stage?.enabled === true) {
24983
+ fail(`Invalid publisher.json retrieval.${key}.enabled: true needs an LLM, and this server has none configured. ` + `Fix: set retrieval.llm in publisher.config.json and LLM_API_KEY in the server's environment, ` + `or set "enabled": "auto" (on only when an LLM is configured) or false.`);
24984
+ }
24985
+ }
24986
+ }
24987
+
24268
24988
  // src/package_load/package_load_worker.ts
24269
24989
  init_source_extraction();
24270
24990
  init_given();
@@ -24324,9 +25044,9 @@ function newRpcId() {
24324
25044
  }
24325
25045
  function callMain(send) {
24326
25046
  const requestId = newRpcId();
24327
- return new Promise((resolve2, reject2) => {
25047
+ return new Promise((resolve3, reject2) => {
24328
25048
  pendingRpc.set(requestId, {
24329
- resolve: (value) => resolve2(value),
25049
+ resolve: (value) => resolve3(value),
24330
25050
  reject: reject2
24331
25051
  });
24332
25052
  send(requestId);
@@ -24446,10 +25166,10 @@ function makeWorkerUrlReader(job) {
24446
25166
  readURL: async (url) => {
24447
25167
  if (url.protocol === "file:") {
24448
25168
  const filePath = fileURLToPath2(url);
24449
- if (job.replacement && path2.resolve(filePath) === path2.resolve(job.packagePath, job.replacement.modelPath)) {
25169
+ if (job.replacement && path3.resolve(filePath) === path3.resolve(job.packagePath, job.replacement.modelPath)) {
24450
25170
  return job.replacement.source;
24451
25171
  }
24452
- return fs2.promises.readFile(filePath, "utf8");
25172
+ return fs3.promises.readFile(filePath, "utf8");
24453
25173
  }
24454
25174
  const response = await callMain((requestId) => {
24455
25175
  const req = {
@@ -24499,8 +25219,8 @@ function buildWorkerMalloyConfig(job) {
24499
25219
  return config;
24500
25220
  }
24501
25221
  async function readPackageMetadata(packagePath, modelPaths) {
24502
- const manifestPath = path2.join(packagePath, PACKAGE_MANIFEST_NAME);
24503
- const contents = await fs2.promises.readFile(manifestPath, "utf8");
25222
+ const manifestPath = path3.join(packagePath, PACKAGE_MANIFEST_NAME);
25223
+ const contents = await fs3.promises.readFile(manifestPath, "utf8");
24504
25224
  let parsed;
24505
25225
  try {
24506
25226
  parsed = JSON.parse(contents);
@@ -24531,12 +25251,13 @@ async function readPackageMetadata(packagePath, modelPaths) {
24531
25251
  manifestLocation: typeof parsed.manifestLocation === "string" ? parsed.manifestLocation : null,
24532
25252
  materialization: materializationWithQueryMetadata(parsePackageMaterialization(parsed.materialization), queryMetadata.queryMetadata),
24533
25253
  scope: scope.scope,
24534
- manifestWarnings: manifestWarnings.length > 0 ? manifestWarnings : undefined
25254
+ manifestWarnings: manifestWarnings.length > 0 ? manifestWarnings : undefined,
25255
+ retrieval: await readPackageRetrieval(packagePath, parsed.retrieval)
24535
25256
  };
24536
25257
  }
24537
25258
  async function listPackageFiles(packagePath) {
24538
25259
  const files = await import_recursive_readdir.default(packagePath, [ignoreDotfiles]);
24539
- return files.map((full) => path2.relative(packagePath, full).replace(/\\/g, "/")).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
25260
+ return files.map((full) => path3.relative(packagePath, full).replace(/\\/g, "/")).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
24540
25261
  }
24541
25262
  function filterModelPaths(allRelative) {
24542
25263
  return allRelative.filter((p) => p.endsWith(MODEL_FILE_SUFFIX) || p.endsWith(NOTEBOOK_FILE_SUFFIX));
@@ -24605,7 +25326,7 @@ function buildRuntimeForModel(job, malloyConfig) {
24605
25326
  }
24606
25327
  async function compileMalloyModel(job, malloyConfig, modelPath) {
24607
25328
  const compileStart = performance.now();
24608
- const fullPath = path2.join(job.packagePath, modelPath);
25329
+ const fullPath = path3.join(job.packagePath, modelPath);
24609
25330
  const modelURL = pathToFileURL(fullPath);
24610
25331
  const importBaseURL = new URL(".", modelURL);
24611
25332
  const { runtime, urlReader, textFor } = buildRuntimeForModel(job, malloyConfig);
@@ -24655,7 +25376,7 @@ async function compileMalloyModel(job, malloyConfig, modelPath) {
24655
25376
  modelPath,
24656
25377
  modelType: "model",
24657
25378
  modelDef,
24658
- modelInfo: modelDefToModelInfo2(modelDef),
25379
+ modelInfo: modelInfoOf(modelDef),
24659
25380
  sourceInfos,
24660
25381
  sources,
24661
25382
  queries,
@@ -24670,12 +25391,12 @@ async function compileMalloyModel(job, malloyConfig, modelPath) {
24670
25391
  }
24671
25392
  async function compileNotebookModel(job, malloyConfig, modelPath) {
24672
25393
  const compileStart = performance.now();
24673
- const fullPath = path2.join(job.packagePath, modelPath);
25394
+ const fullPath = path3.join(job.packagePath, modelPath);
24674
25395
  const modelURL = pathToFileURL(fullPath);
24675
25396
  const importBaseURL = new URL(".", modelURL);
24676
25397
  const { runtime, urlReader } = buildRuntimeForModel(job, malloyConfig);
24677
25398
  const authorizeWarningCollection = authorizeWarningCollector();
24678
- const fileContents = await fs2.promises.readFile(modelURL, "utf8");
25399
+ const fileContents = await fs3.promises.readFile(modelURL, "utf8");
24679
25400
  const parse = MalloySQLParser.parse(fileContents, modelPath);
24680
25401
  let mm;
24681
25402
  const perCellMM = parse.statements.map((stmt) => {
@@ -24699,7 +25420,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
24699
25420
  continue;
24700
25421
  }
24701
25422
  const currentModelDef = (await localMM.getModel())._modelDef;
24702
- const currentInfo = modelDefToModelInfo2(currentModelDef);
25423
+ const currentInfo = modelInfoOf(currentModelDef);
24703
25424
  const newSources = collectSourceInfos(currentModelDef).filter((s) => !(s.name in oldSources));
24704
25425
  for (const s of newSources)
24705
25426
  oldSources[s.name] = s;
@@ -24781,7 +25502,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
24781
25502
  modelPath,
24782
25503
  modelType: "notebook",
24783
25504
  modelDef: finalModelDef,
24784
- modelInfo: finalModelDef ? modelDefToModelInfo2(finalModelDef) : undefined,
25505
+ modelInfo: finalModelDef ? modelInfoOf(finalModelDef) : undefined,
24785
25506
  sourceInfos: finalSourceInfos,
24786
25507
  sources: finalSources,
24787
25508
  queries: finalQueries,