@malloy-publisher/server 0.9.0 → 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 (32) hide show
  1. package/README.docker.md +46 -5
  2. package/dist/app/api-doc.yaml +144 -54
  3. package/dist/app/assets/EnvironmentPage-B-t_QP32.js +1 -0
  4. package/dist/app/assets/{HomePage-D3MvQaZn.js → HomePage-Cpu2zMy-.js} +1 -1
  5. package/dist/app/assets/{LightMode-e1lNBfpL.js → LightMode-DOBl6awB.js} +1 -1
  6. package/dist/app/assets/MainPage-C7ySrclP.js +1 -0
  7. package/dist/app/assets/ModelPage-D8r_J-N9.js +2 -0
  8. package/dist/app/assets/PackagePage-CcVFBhnT.js +1 -0
  9. package/dist/app/assets/RenderedResult-CBLHr32C.es-DRr9Gmsz.js +141 -0
  10. package/dist/app/assets/{RouteError-BQnzPJvb.js → RouteError-mgOkv7XK.js} +1 -1
  11. package/dist/app/assets/ThemeEditorPage-CIPDs2w9.js +1 -0
  12. package/dist/app/assets/ToggleButtonGroup-CEZThrzh.js +1 -0
  13. package/dist/app/assets/{index-CCNqF0-G.js → index-Bg-M8sZz.js} +1 -1
  14. package/dist/app/assets/index-CGdwupPP.js +457 -0
  15. package/dist/app/assets/{index-CKmZ4HG7.js → index-CeLTZAFW.js} +116 -116
  16. package/dist/app/assets/{index-CdExa3Pk.js → index-DoF8ihra.js} +1 -1
  17. package/dist/app/assets/{index-ufFZR_K-.js → index-IC0jPCkR.js} +1 -1
  18. package/dist/app/assets/index.es-6eARPKZf.js +198 -0
  19. package/dist/app/index.html +1 -1
  20. package/dist/package_load_worker.mjs +571 -107
  21. package/dist/server.mjs +10356 -9719
  22. package/package.json +1 -1
  23. package/dist/app/assets/EnvironmentPage-UMS7Py4Q.js +0 -1
  24. package/dist/app/assets/MainPage-B0ozqtQO.js +0 -2
  25. package/dist/app/assets/ModelPage-lQj4Axqu.js +0 -2
  26. package/dist/app/assets/PackagePage-BT0YKNKU.js +0 -1
  27. package/dist/app/assets/RenderedResult-BRgIgxWh.es-DiE7kPNX.js +0 -136
  28. package/dist/app/assets/ThemeEditorPage-D6t7w9Js.js +0 -1
  29. package/dist/app/assets/ToggleButtonGroup-CJT2JKTC.js +0 -1
  30. package/dist/app/assets/index-TiWjmdoW.js +0 -450
  31. package/dist/app/assets/index.es-iUV0gCYk.js +0 -111
  32. package/dist/cpufeatures-1yrn0vtw.node +0 -0
@@ -13058,9 +13058,54 @@ function logInternalFailure(summary, error, level = "error") {
13058
13058
  }
13059
13059
  });
13060
13060
  }
13061
- function internalErrorToHttpError(error) {
13061
+ function filesystemAccessFailure(error) {
13062
+ let current = error;
13063
+ for (let depth = 0;depth < 8 && current instanceof Error; depth++) {
13064
+ const errno = current;
13065
+ if (typeof errno.code === "string" && errno.code in FILESYSTEM_ACCESS_DESCRIPTIONS && typeof errno.syscall === "string") {
13066
+ return errno;
13067
+ }
13068
+ current = errno.cause;
13069
+ }
13070
+ return;
13071
+ }
13072
+ function describeFilesystemAccessFailure(errno) {
13073
+ const code = errno.code ?? "";
13074
+ const target = errno.path ? ` '${errno.path}'` : "";
13075
+ return `${code}: ${FILESYSTEM_ACCESS_DESCRIPTIONS[code] ?? code}, ${errno.syscall}${target}`;
13076
+ }
13077
+ function messageWithFilesystemCause(error) {
13078
+ const message = error instanceof Error ? error.message : String(error);
13079
+ const access = filesystemAccessFailure(error);
13080
+ if (!access || message.includes(access.code ?? ""))
13081
+ return message;
13082
+ return `${message}: ${describeFilesystemAccessFailure(access)}`;
13083
+ }
13084
+ function errnoWireFields(error) {
13085
+ const { code, syscall, path } = error;
13086
+ if (typeof code !== "string")
13087
+ return;
13088
+ return {
13089
+ code,
13090
+ ...typeof syscall === "string" ? { syscall } : {},
13091
+ ...typeof path === "string" ? { path } : {}
13092
+ };
13093
+ }
13094
+ function internalErrorToHttpError(error, { log = true } = {}) {
13095
+ const logInternal = (...args) => {
13096
+ if (log)
13097
+ logInternalFailure(...args);
13098
+ };
13099
+ const access = filesystemAccessFailure(error);
13100
+ if (access) {
13101
+ logInternal("Filesystem access refused", error, "warn");
13102
+ return httpError(500, `The server cannot access a path it needs (${describeFilesystemAccessFailure(access)}). ` + `Give the user the server runs as access to it.`);
13103
+ }
13062
13104
  if (error instanceof BadRequestError) {
13063
13105
  return httpError(400, error.message);
13106
+ } else if (error instanceof ServerConfigurationError) {
13107
+ logInternal("Server configuration error", error, "warn");
13108
+ return httpError(500, error.message);
13064
13109
  } else if (error instanceof FrozenConfigError) {
13065
13110
  return httpError(403, error.message);
13066
13111
  } else if (error instanceof AccessDeniedError) {
@@ -13106,7 +13151,7 @@ function internalErrorToHttpError(error) {
13106
13151
  if (error.callerSafe) {
13107
13152
  return httpError(502, error.message);
13108
13153
  }
13109
- logInternalFailure("Upstream connection error", error, "warn");
13154
+ logInternal("Upstream connection error", error, "warn");
13110
13155
  return httpError(502, GENERIC_UPSTREAM_MESSAGE);
13111
13156
  } else if (error instanceof MaterializationNotFoundError) {
13112
13157
  return httpError(404, error.message);
@@ -13117,7 +13162,7 @@ function internalErrorToHttpError(error) {
13117
13162
  } else if (error instanceof WriteConflictError) {
13118
13163
  return httpError(409, error.message);
13119
13164
  } else if (error instanceof WriteRolledBackError) {
13120
- logInternalFailure("Dashboard write rolled back", error, "warn");
13165
+ logInternal("Dashboard write rolled back", error, "warn");
13121
13166
  return httpError(500, error.message);
13122
13167
  } else if (error instanceof ServiceUnavailableError) {
13123
13168
  return httpError(503, error.message);
@@ -13128,7 +13173,7 @@ function internalErrorToHttpError(error) {
13128
13173
  } else if (error instanceof NotImplementedError) {
13129
13174
  return httpError(501, error.message);
13130
13175
  } else {
13131
- logInternalFailure("Unhandled internal error", error);
13176
+ logInternal("Unhandled internal error", error);
13132
13177
  return httpError(500, GENERIC_INTERNAL_MESSAGE);
13133
13178
  }
13134
13179
  }
@@ -13150,10 +13195,15 @@ function toQueryTextProblem(problem) {
13150
13195
  ...problem.at ? { at: { range: problem.at.range } } : {}
13151
13196
  };
13152
13197
  }
13153
- var GENERIC_INTERNAL_MESSAGE = "Internal server error.", GENERIC_UPSTREAM_MESSAGE = "Upstream connection error.", MAX_LOGGED_DETAIL_CHARS = 2000, NotImplementedError, BadRequestError, InvalidArgumentError, CompileRefusedError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, ConnectionNotFoundError, TableNotFoundError, ConnectionError, DestinationNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, PackageManifestError, MaterializationEligibilityError, PublisherConfigError, 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;
13154
13199
  var init_errors = __esm(() => {
13155
13200
  init_constants();
13156
13201
  init_logger();
13202
+ FILESYSTEM_ACCESS_DESCRIPTIONS = {
13203
+ EACCES: "permission denied",
13204
+ EPERM: "operation not permitted",
13205
+ EROFS: "read-only file system"
13206
+ };
13157
13207
  NotImplementedError = class NotImplementedError extends Error {
13158
13208
  constructor(message) {
13159
13209
  super(message);
@@ -13174,8 +13224,8 @@ var init_errors = __esm(() => {
13174
13224
  }
13175
13225
  };
13176
13226
  PackageNotFoundError = class PackageNotFoundError extends Error {
13177
- constructor(message) {
13178
- super(message);
13227
+ constructor(message, options) {
13228
+ super(message, options);
13179
13229
  }
13180
13230
  };
13181
13231
  ModelNotFoundError = class ModelNotFoundError extends Error {
@@ -13246,6 +13296,12 @@ var init_errors = __esm(() => {
13246
13296
  this.cause = cause;
13247
13297
  }
13248
13298
  };
13299
+ ServerConfigurationError = class ServerConfigurationError extends Error {
13300
+ constructor(message) {
13301
+ super(message);
13302
+ this.name = "ServerConfigurationError";
13303
+ }
13304
+ };
13249
13305
  FrozenConfigError = class FrozenConfigError extends Error {
13250
13306
  constructor(message = `Publisher config can't be updated when ${PUBLISHER_CONFIG_NAME} has { "frozenConfig": true }`) {
13251
13307
  super(message);
@@ -13292,19 +13348,25 @@ var init_errors = __esm(() => {
13292
13348
  }
13293
13349
  };
13294
13350
  WriteRolledBackError = class WriteRolledBackError extends Error {
13295
- constructor(message) {
13296
- super(message);
13351
+ constructor(message, options) {
13352
+ super(message, options);
13297
13353
  this.name = "WriteRolledBackError";
13298
13354
  }
13299
13355
  };
13356
+ WriteVerifyError = class WriteVerifyError extends Error {
13357
+ constructor(message, options) {
13358
+ super(message, options);
13359
+ this.name = "WriteVerifyError";
13360
+ }
13361
+ };
13300
13362
  InvalidStateTransitionError = class InvalidStateTransitionError extends Error {
13301
13363
  constructor(message) {
13302
13364
  super(message);
13303
13365
  }
13304
13366
  };
13305
13367
  ServiceUnavailableError = class ServiceUnavailableError extends Error {
13306
- constructor(message) {
13307
- super(message);
13368
+ constructor(message, options) {
13369
+ super(message, options);
13308
13370
  }
13309
13371
  };
13310
13372
  PayloadTooLargeError = class PayloadTooLargeError extends Error {
@@ -13351,6 +13413,7 @@ var init_authorize_routes = __esm(() => {
13351
13413
 
13352
13414
  // src/service/authorize.ts
13353
13415
  import { payloadOf, routeOf } from "@malloydata/malloy";
13416
+ import { createRequire as createRequire2 } from "module";
13354
13417
  function noteRoute(text) {
13355
13418
  return routeOf({ value: text.trimStart() });
13356
13419
  }
@@ -13399,7 +13462,7 @@ function motlyAuthorizePayloadPattern(route) {
13399
13462
  return new RegExp(`^[ \\t]*[([{<][ \\t]*(?:${nearMissWordAlternation(route)})[ \\t]*[)\\]}>]`, "iu");
13400
13463
  }
13401
13464
  function malformedAuthorizeAttemptPattern(route) {
13402
- return new RegExp(`^##?\\|?[ \\t]*[([{<]?[ \\t]*(?:${nearMissWordAlternation(route)})`, "iu");
13465
+ return new RegExp(`^##?\\|?[ \\t]*(?:[([{<][ \\t]*)?(?:${nearMissWordAlternation(route)})`, "iu");
13403
13466
  }
13404
13467
  function authorizeNoteContent(text) {
13405
13468
  const route = canonicalAuthorizeRoute(noteRoute(text));
@@ -13410,12 +13473,144 @@ function authorizeNoteContent(text) {
13410
13473
  function authorizeAnnotationRoute(text) {
13411
13474
  return authorizeNoteContent(text)?.route;
13412
13475
  }
13413
- function hasCallerAuthorizeAnnotation(callerText) {
13414
- 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
+ }
13415
13484
  }
13416
- function assertNoCallerAuthorizeAnnotation(callerText) {
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();
13606
+ }
13607
+ function assertNoAuthorizeTagLike(callerText) {
13417
13608
  if (!AUTHORIZE_ANNOTATION_ANYWHERE.test(callerText))
13418
13609
  return;
13610
+ lastRefusalKind = "whole_text";
13611
+ refuseCallerAuthorizeAnnotation();
13612
+ }
13613
+ function refuseCallerAuthorizeAnnotation() {
13419
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.");
13420
13615
  }
13421
13616
  function containsAuthorizeAnnotationTag(texts) {
@@ -13613,13 +13808,19 @@ ${rewrites}
13613
13808
  ` + `Test that the rewrite gates the same rows and check the row count ` + `matches what the string form served.`
13614
13809
  });
13615
13810
  }
13616
- 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;
13617
13812
  var init_authorize = __esm(() => {
13618
13813
  init_errors();
13619
13814
  init_authorize_routes();
13620
- 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]|$)`;
13621
13816
  AUTHORIZE_ANNOTATION_ANYWHERE = new RegExp(AUTHORIZE_TAG_LIKE, "iu");
13622
13817
  REGEXP_SPECIAL = /[.*+?^${}()|[\]\\]/g;
13818
+ PROSE_ROUTES = new Set(["markdown", "text"]);
13819
+ parserLoader = loadMalloyParser;
13820
+ lastLexed = {
13821
+ text: "",
13822
+ prose: []
13823
+ };
13623
13824
  GIVEN_REF_PATTERN = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
13624
13825
  SINGLE_QUOTE_ESCAPE = /\\(['\\])/g;
13625
13826
  DOUBLE_QUOTE_ESCAPE = /\\(["\\])/g;
@@ -13632,9 +13833,12 @@ var init_authorize = __esm(() => {
13632
13833
  // src/authorize_metrics.ts
13633
13834
  function recordAuthorizeGuardRejection(field) {
13634
13835
  guardRejectionCounter ??= publisherMeter().createCounter("publisher_authorize_guard_rejected_total", {
13635
- 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()
13636
13841
  });
13637
- guardRejectionCounter.add(1, { field });
13638
13842
  }
13639
13843
  function recordAuthorizeBypass(entryPoint) {
13640
13844
  bypassCounter ??= publisherMeter().createCounter("publisher_authorize_bypass_total", {
@@ -13723,6 +13927,76 @@ var init_data_styles = __esm(() => {
13723
13927
  init_logger();
13724
13928
  });
13725
13929
 
13930
+ // src/package_load/error_wire.ts
13931
+ import { MalloyError as MalloyError2 } from "@malloydata/malloy";
13932
+ function serializeError(error) {
13933
+ const serialized = serializeErrorShape(error);
13934
+ const errno = error instanceof Error ? errnoWireFields(error) : undefined;
13935
+ return errno ? { ...serialized, errno } : serialized;
13936
+ }
13937
+ function serializeErrorShape(error) {
13938
+ if (error instanceof MalloyError2) {
13939
+ return {
13940
+ name: error.name,
13941
+ message: error.message,
13942
+ stack: error.stack,
13943
+ malloyProblems: error.problems,
13944
+ isCompilationError: true
13945
+ };
13946
+ }
13947
+ if (error instanceof ModelCompilationError) {
13948
+ return {
13949
+ name: error.name,
13950
+ message: error.message,
13951
+ stack: error.stack,
13952
+ isCompilationError: true
13953
+ };
13954
+ }
13955
+ if (error instanceof PackageManifestError) {
13956
+ return {
13957
+ name: error.name,
13958
+ message: error.message,
13959
+ stack: error.stack,
13960
+ isManifestError: true
13961
+ };
13962
+ }
13963
+ if (error instanceof Error) {
13964
+ return {
13965
+ name: error.name,
13966
+ message: error.message,
13967
+ stack: error.stack
13968
+ };
13969
+ }
13970
+ return { name: "Error", message: String(error) };
13971
+ }
13972
+ function deserializeError(serialized) {
13973
+ const err = new Error(serialized.message);
13974
+ err.name = serialized.name;
13975
+ if (serialized.stack)
13976
+ err.stack = serialized.stack;
13977
+ if (serialized.errno)
13978
+ Object.assign(err, serialized.errno);
13979
+ if (serialized.malloyProblems) {
13980
+ err.problems = serialized.malloyProblems;
13981
+ }
13982
+ if (serialized.isCompilationError) {
13983
+ const wrapped = new ModelCompilationError(err);
13984
+ if (serialized.stack)
13985
+ wrapped.stack = serialized.stack;
13986
+ return wrapped;
13987
+ }
13988
+ if (serialized.isManifestError) {
13989
+ const manifestError = new PackageManifestError(serialized.message);
13990
+ if (serialized.stack)
13991
+ manifestError.stack = serialized.stack;
13992
+ return manifestError;
13993
+ }
13994
+ return err;
13995
+ }
13996
+ var init_error_wire = __esm(() => {
13997
+ init_errors();
13998
+ });
13999
+
13726
14000
  // src/service/annotations.ts
13727
14001
  import { Annotations } from "@malloydata/malloy";
13728
14002
  function isReservedRoute(route) {
@@ -14691,6 +14965,44 @@ var init_gate_classification = __esm(() => {
14691
14965
  init_gate_registry_walk();
14692
14966
  });
14693
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
+
14694
15006
  // src/service/query_text.ts
14695
15007
  import { ParseUtil } from "@malloydata/malloy-tag";
14696
15008
  function extractRunTargetSourceName(query) {
@@ -15622,6 +15934,17 @@ function isNotebookModelPath(modelPath) {
15622
15934
  const segments = modelPath.split("/");
15623
15935
  return segments.length === 2 && segments[0] === NOTEBOOKS_DIR;
15624
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
+ }
15625
15948
  function routeOfNote(text) {
15626
15949
  return routeOf3({ value: text.trimStart() });
15627
15950
  }
@@ -15649,34 +15972,71 @@ function dashboardDescriptionNotes(notes) {
15649
15972
  return notes.filter((n) => line !== undefined && (n.at?.range.start.line ?? 0) > line).map((n) => n.text);
15650
15973
  }
15651
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) {
15652
16004
  const lines = source.split(/\r\n|\r|\n/);
16005
+ const closerAfter = blockCloserFinder(lines);
15653
16006
  for (let i = 0;i < lines.length; i++) {
15654
16007
  const trimmed = lines[i].trimStart();
15655
16008
  const opener = /^(#{1,2})\|/.exec(trimmed);
15656
16009
  if (opener) {
15657
- const wanted = `|${opener[1]}`;
15658
- if (trimmed.includes(wanted, opener[0].length))
15659
- continue;
15660
16010
  const column = lines[i].length - trimmed.length;
15661
- let end = -1;
15662
- for (let at = i + 1;at < lines.length; at++)
15663
- if (closesBlock(lines[at], 0, column, wanted)) {
15664
- end = at;
15665
- break;
15666
- }
16011
+ const end = closerAfter(column, `|${opener[1]}`, i);
15667
16012
  if (end !== -1) {
16013
+ const block = [trimmed, ...lines.slice(i + 1, end)].join(`
16014
+ `);
16015
+ if (artifactLine.test(block))
16016
+ return block;
15668
16017
  i = end;
15669
16018
  continue;
15670
16019
  }
15671
16020
  }
15672
16021
  if (artifactLine.test(trimmed))
15673
- return true;
16022
+ return trimmed;
15674
16023
  }
15675
- 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");
15676
16029
  }
15677
16030
  function claimsToBeANotebook(source) {
15678
16031
  return hasArtifactLineOutsideBlocks(source, ARTIFACT_NOTE);
15679
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
+ }
15680
16040
  function parseNotebookText(text) {
15681
16041
  try {
15682
16042
  const { problems, parse } = translateToParse(text);
@@ -16113,9 +16473,12 @@ function readMarkdownBlocks(parse, text) {
16113
16473
  return blocks;
16114
16474
  }
16115
16475
  function notebookReaderProblem(modelPath, text, modelDef, url) {
16116
- if (!isNotebookModelPath(modelPath))
16476
+ if (!isDocumentModelPath(modelPath))
16117
16477
  return;
16118
- if (artifactNoteLine(ownModelNoteObjects(modelDef)) === undefined)
16478
+ const notes = ownModelNoteObjects(modelDef);
16479
+ if (artifactNoteLine(notes) === undefined)
16480
+ return;
16481
+ if (documentKind(modelPath, artifactKindOfNotes(notes)) !== "notebook")
16119
16482
  return;
16120
16483
  const parse = parseNotebookText(text);
16121
16484
  const error = isNotebookReaderError(parse) ? parse : readNotebookCells(parse, modelDef, text).error;
@@ -16135,7 +16498,7 @@ function notebookReaderProblem(modelPath, text, modelDef, url) {
16135
16498
  }
16136
16499
  };
16137
16500
  }
16138
- 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) => {
16139
16502
  const accessor = node[name];
16140
16503
  return typeof accessor === "function" ? accessor.call(node) : undefined;
16141
16504
  }, STATEMENT_ACCESSORS, NOTEBOOK_PARSE_URL = "file:///publisher-notebook-reader/notebook.malloy", NOTE_TOKENS, TEXT_BLOCK_NAME;
@@ -16144,7 +16507,62 @@ var init_notebook = __esm(() => {
16144
16507
  init_annotations();
16145
16508
  init_query_text();
16146
16509
  init_motly();
16147
- 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])/;
16148
16566
  STATEMENT_ACCESSORS = [
16149
16567
  ["runStatement", "run"],
16150
16568
  ["docAnnotations", "notes"],
@@ -22962,9 +23380,12 @@ var init_given = __esm(() => {
22962
23380
  });
22963
23381
 
22964
23382
  // src/service/dashboard.ts
22965
- 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
+ }
22966
23387
  function filterPublisherOwnedRenderLogs(logs, modelPath) {
22967
- const couldBeATile = isDashboardModelPath(modelPath);
23388
+ const couldBeATile = isDocumentModelPath(modelPath);
22968
23389
  return logs.filter((log) => {
22969
23390
  const match = UNKNOWN_RENDER_TAG.exec(log.message ?? "");
22970
23391
  if (!match)
@@ -22989,6 +23410,24 @@ function dashboardSlug(modelPath) {
22989
23410
  function matchesDocumentedDashboardName(slug) {
22990
23411
  return /^[a-zA-Z0-9_-]+$/.test(slug);
22991
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
+ }
22992
23431
  function readArtifactTag(tag, declaredType) {
22993
23432
  const artifact = tag.tag("artifact");
22994
23433
  if (!artifact)
@@ -22997,12 +23436,25 @@ function readArtifactTag(tag, declaredType) {
22997
23436
  const autorun = readAutorun(artifact);
22998
23437
  return {
22999
23438
  title: tagText(artifact, "title"),
23000
- 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),
23001
23441
  dashboardColumns: tag.tag("dashboard")?.has("columns") ? positiveInteger(tagNumeric(tag.tag("dashboard"), "columns")) : artifact.array("tiles") ? positiveInteger(tagNumeric(artifact, "dashboard_columns")) : undefined,
23002
23442
  givens,
23003
23443
  autorun
23004
23444
  };
23005
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
+ }
23006
23458
  function positiveInteger(value) {
23007
23459
  return value !== undefined && Number.isInteger(value) && value >= 1 ? value : undefined;
23008
23460
  }
@@ -23050,6 +23502,11 @@ function readDashboardModelFacts(modelPath, modelDef, surfacedGivenNames, gatesB
23050
23502
  for (const field of obj.fields) {
23051
23503
  const fieldName = field.as || field.name;
23052
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
+ }
23053
23510
  if (field.type === "turtle") {
23054
23511
  const refs = new Set(sourceGivens);
23055
23512
  collectGivenRefs(field.pipeline, refs);
@@ -23120,7 +23577,11 @@ function tagTextArray(tag, ...path) {
23120
23577
  return items?.map((item) => tagText(item)).filter(isString);
23121
23578
  }
23122
23579
  function resolveTileGivens(tile, facts) {
23123
- 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));
23124
23585
  }
23125
23586
  function resolveTileLayout(tile, facts) {
23126
23587
  const annotations = tileAnnotations(tile, facts);
@@ -23132,7 +23593,15 @@ function tileAnnotations(tile, facts) {
23132
23593
  }
23133
23594
  function referencedTileGivens(tile, facts) {
23134
23595
  const normalized = normalizeTileExpression(tile);
23135
- 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));
23136
23605
  }
23137
23606
  function buildGivenSpecs(names, facts) {
23138
23607
  const declarations = facts.givens;
@@ -23174,26 +23643,41 @@ function buildDashboardManifest(facts) {
23174
23643
  const modelTag = motlyTag(facts.modelAnnotations);
23175
23644
  const declaredType = (name2) => facts.givens.get(name2)?.type;
23176
23645
  const composite = modelTag ? readArtifactTag(modelTag, declaredType) : undefined;
23177
- if (composite?.tiles?.length) {
23178
- 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;
23179
23660
  const givenNames = resolveTileGivens(query, facts);
23180
23661
  const layout = resolveTileLayout(query, facts);
23181
23662
  return {
23663
+ kind: "query",
23182
23664
  query,
23183
23665
  ...givenNames ? { givenNames } : {},
23184
23666
  ...layout
23185
23667
  };
23186
23668
  });
23669
+ const queries = queryTiles(tiles);
23187
23670
  const doc = docCommentTitleAndDescription(facts.descriptionNotes ?? facts.modelAnnotations, composite.title);
23188
23671
  return {
23189
23672
  ...base,
23673
+ kind,
23190
23674
  title: doc.title ?? name,
23191
23675
  description: doc.description,
23192
23676
  tiles,
23193
- dashboardColumns: composite.dashboardColumns,
23677
+ dashboardColumns: kind === "notebook" ? 1 : composite.dashboardColumns,
23194
23678
  startingGivens: composite.givens,
23195
23679
  autorun: composite.autorun,
23196
- 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)
23197
23681
  };
23198
23682
  }
23199
23683
  for (const query of facts.queries) {
@@ -23204,6 +23688,7 @@ function buildDashboardManifest(facts) {
23204
23688
  const doc = docCommentTitleAndDescription(query.annotations, artifact.title);
23205
23689
  return {
23206
23690
  ...base,
23691
+ kind: "dashboard",
23207
23692
  title: doc.title ?? name,
23208
23693
  description: doc.description,
23209
23694
  query: query.name,
@@ -23245,19 +23730,33 @@ function lintDashboard(facts, manifest) {
23245
23730
  continue;
23246
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.` : `.`));
23247
23732
  }
23733
+ const textBlocks = readTextBlocks(facts.modelAnnotations);
23248
23734
  for (const entry of artifactTag?.array("tiles") ?? []) {
23249
23735
  const kind = tagText(entry, "kind");
23250
- if (kind === "text") {
23251
- 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")
23252
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.`);
23253
23747
  }
23254
- const carried = Object.keys(entry.dict ?? {}).filter((property) => !(property === "kind" && kind === "query"));
23255
- if (carried.length === 0)
23256
- continue;
23257
- const named = carried.map((property) => `\`${property}\``).join(", ");
23258
- 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\`).`);
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.`);
23750
+ }
23751
+ }
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);
23259
23758
  }
23260
- for (const { query: tile } of manifest.tiles ?? []) {
23759
+ for (const { query: tile } of manifest.kind === "notebook" ? [] : queryTiles(manifest.tiles)) {
23261
23760
  const tag = motlyTag(tileAnnotations(tile, facts) ?? []);
23262
23761
  if (!tag?.has("colspan"))
23263
23762
  continue;
@@ -23276,21 +23775,28 @@ function lintDashboard(facts, manifest) {
23276
23775
  }
23277
23776
  const columnsTag = ownTag?.tag("dashboard");
23278
23777
  if (columnsTag?.has("columns")) {
23279
- if (positiveInteger(tagNumeric(columnsTag, "columns")) === undefined) {
23778
+ const width = positiveInteger(tagNumeric(columnsTag, "columns"));
23779
+ if (width === undefined) {
23280
23780
  const raw = tagText(columnsTag, "columns");
23281
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.`);
23282
23784
  }
23283
23785
  }
23284
- for (const { query: tile } of manifest.tiles ?? []) {
23786
+ for (const { query: tile } of queryTiles(manifest.tiles)) {
23285
23787
  const parts = plainTileParts(tile);
23286
23788
  if (!parts)
23287
23789
  continue;
23288
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
+ }
23289
23795
  if (!resolved) {
23290
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`;
23291
23797
  add(`tile "${tile}" does not resolve: ${detail}.`, "error");
23292
23798
  }
23293
- 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));
23294
23800
  for (const name of unbindable) {
23295
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}.`);
23296
23802
  }
@@ -23453,7 +23959,7 @@ function lintSelfDrills(facts) {
23453
23959
  }
23454
23960
  return Array.from(findings.values());
23455
23961
  }
23456
- 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", …].`;
23457
23963
  var init_dashboard = __esm(() => {
23458
23964
  init_annotations();
23459
23965
  init_notebook();
@@ -23674,9 +24180,8 @@ var init_filter = __esm(() => {
23674
24180
 
23675
24181
  // src/service/source_extraction.ts
23676
24182
  import {
23677
- isJoined as isJoined3,
23678
- isSourceDef as isSourceDef6,
23679
- modelDefToModelInfo
24183
+ isJoined as isJoined4,
24184
+ isSourceDef as isSourceDef6
23680
24185
  } from "@malloydata/malloy";
23681
24186
  function joinFieldNamesUnresolvableDeclaration(field, modelDef) {
23682
24187
  const ids = [field.referenceID, field.sourceID].filter((id) => !!id);
@@ -23707,7 +24212,7 @@ function considerNoteOwner(declaredBy, note, candidate) {
23707
24212
  function collectSourceInfos(modelDef) {
23708
24213
  const names = Object.keys(modelDef.contents).filter((name) => isSourceDef6(modelDef.contents[name]));
23709
24214
  const exports = [...new Set([...modelDef.exports, ...names])];
23710
- return modelDefToModelInfo({ ...modelDef, exports }).entries.filter((entry) => entry.kind === "source");
24215
+ return modelInfoOf({ ...modelDef, exports }).entries.filter((entry) => entry.kind === "source");
23711
24216
  }
23712
24217
  function extractSourcesFromModelDef(modelDef, givens, onParseError) {
23713
24218
  const filterMap = new Map;
@@ -23845,7 +24350,7 @@ function extractSourcesFromModelDef(modelDef, givens, onParseError) {
23845
24350
  continue;
23846
24351
  }
23847
24352
  const fieldName = field.as || field.name;
23848
- if (isJoined3(field) && isSourceDef6(field)) {
24353
+ if (isJoined4(field) && isSourceDef6(field)) {
23849
24354
  const joinedStruct = field;
23850
24355
  if (joinFieldNamesUnresolvableDeclaration(joinedStruct, modelDef)) {
23851
24356
  continue;
@@ -23903,6 +24408,7 @@ var init_source_extraction = __esm(() => {
23903
24408
  init_annotations();
23904
24409
  init_authorize();
23905
24410
  init_authorize_routes();
24411
+ init_model_info();
23906
24412
  init_filter();
23907
24413
  init_gate_registry_walk();
23908
24414
  });
@@ -23935,16 +24441,16 @@ init_constants();
23935
24441
  init_authorize_metrics();
23936
24442
  init_data_styles();
23937
24443
  init_errors();
24444
+ init_error_wire();
23938
24445
  init_authorize();
23939
24446
  init_gate_classification();
23940
24447
  init_gate_dimension();
24448
+ init_model_info();
23941
24449
  var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
23942
24450
  import {
23943
24451
  contextOverlay,
23944
24452
  isSourceDef as isSourceDef7,
23945
24453
  MalloyConfig,
23946
- MalloyError as MalloyError2,
23947
- modelDefToModelInfo as modelDefToModelInfo2,
23948
24454
  Runtime
23949
24455
  } from "@malloydata/malloy";
23950
24456
  import {
@@ -24529,7 +25035,7 @@ async function compileMalloyModel(job, malloyConfig, modelPath) {
24529
25035
  modelPath,
24530
25036
  modelType: "model",
24531
25037
  modelDef,
24532
- modelInfo: modelDefToModelInfo2(modelDef),
25038
+ modelInfo: modelInfoOf(modelDef),
24533
25039
  sourceInfos,
24534
25040
  sources,
24535
25041
  queries,
@@ -24573,7 +25079,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
24573
25079
  continue;
24574
25080
  }
24575
25081
  const currentModelDef = (await localMM.getModel())._modelDef;
24576
- const currentInfo = modelDefToModelInfo2(currentModelDef);
25082
+ const currentInfo = modelInfoOf(currentModelDef);
24577
25083
  const newSources = collectSourceInfos(currentModelDef).filter((s) => !(s.name in oldSources));
24578
25084
  for (const s of newSources)
24579
25085
  oldSources[s.name] = s;
@@ -24655,7 +25161,7 @@ async function compileNotebookModel(job, malloyConfig, modelPath) {
24655
25161
  modelPath,
24656
25162
  modelType: "notebook",
24657
25163
  modelDef: finalModelDef,
24658
- modelInfo: finalModelDef ? modelDefToModelInfo2(finalModelDef) : undefined,
25164
+ modelInfo: finalModelDef ? modelInfoOf(finalModelDef) : undefined,
24659
25165
  sourceInfos: finalSourceInfos,
24660
25166
  sources: finalSources,
24661
25167
  queries: finalQueries,
@@ -24722,48 +25228,6 @@ async function loadPackage(job) {
24722
25228
  }
24723
25229
  };
24724
25230
  }
24725
- function serializeError(error) {
24726
- if (error instanceof MalloyError2) {
24727
- return {
24728
- name: error.name,
24729
- message: error.message,
24730
- stack: error.stack,
24731
- malloyProblems: error.problems,
24732
- isCompilationError: true
24733
- };
24734
- }
24735
- if (error instanceof ModelCompilationError) {
24736
- return {
24737
- name: error.name,
24738
- message: error.message,
24739
- stack: error.stack,
24740
- isCompilationError: true
24741
- };
24742
- }
24743
- if (error instanceof PackageManifestError) {
24744
- return {
24745
- name: error.name,
24746
- message: error.message,
24747
- stack: error.stack,
24748
- isManifestError: true
24749
- };
24750
- }
24751
- if (error instanceof Error) {
24752
- return {
24753
- name: error.name,
24754
- message: error.message,
24755
- stack: error.stack
24756
- };
24757
- }
24758
- return { name: "Error", message: String(error) };
24759
- }
24760
- function deserializeError(serialized) {
24761
- const err = new Error(serialized.message);
24762
- err.name = serialized.name;
24763
- if (serialized.stack)
24764
- err.stack = serialized.stack;
24765
- return err;
24766
- }
24767
25231
  var shuttingDown = false;
24768
25232
  var inFlightJobs = new Set;
24769
25233
  port.on("message", (message) => {