@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.
- package/README.docker.md +14 -0
- package/dist/app/api-doc.yaml +414 -98
- package/dist/app/assets/EnvironmentPage-uo3igcP9.js +1 -0
- package/dist/app/assets/{HomePage-D3MvQaZn.js → HomePage-0OQVchl4.js} +1 -1
- package/dist/app/assets/MainPage-D4SOACdm.js +1 -0
- package/dist/app/assets/ModelPage-B5CpdEDl.js +2 -0
- package/dist/app/assets/PackagePage-BzUfTQKy.js +1 -0
- package/dist/app/assets/RenderedResult-DwB0-9Yx.es-1MzhmBXH.js +141 -0
- package/dist/app/assets/{RouteError-BQnzPJvb.js → RouteError-BXcrPjvD.js} +1 -1
- package/dist/app/assets/ThemeEditorPage-FpYg5NPt.js +1 -0
- package/dist/app/assets/{ToggleButtonGroup-CJT2JKTC.js → ToggleButtonGroup-Vkkjsbxx.js} +1 -1
- package/dist/app/assets/documentRoutes-BWHzvbNO.js +1 -0
- package/dist/app/assets/index-BZEvIUf-.js +484 -0
- package/dist/app/assets/index-CprgMbE_.js +1 -0
- package/dist/app/assets/{index-ufFZR_K-.js → index-DFvJYp4Q.js} +7 -7
- package/dist/app/assets/index-NVPzfbUZ.js +1317 -0
- package/dist/app/assets/{index-ireHUsv_.js → index-sKcoT1e_.js} +422 -427
- package/dist/app/assets/index.es-D2Ts8adM.js +195 -0
- package/dist/app/index.html +1 -1
- package/dist/package_load_worker.mjs +801 -80
- package/dist/server.mjs +21707 -17510
- package/package.json +15 -15
- package/dist/app/assets/EnvironmentPage-UMS7Py4Q.js +0 -1
- package/dist/app/assets/LightMode-e1lNBfpL.js +0 -1
- package/dist/app/assets/MainPage-B0ozqtQO.js +0 -2
- package/dist/app/assets/ModelPage-lQj4Axqu.js +0 -2
- package/dist/app/assets/PackagePage-BT0YKNKU.js +0 -1
- package/dist/app/assets/RenderedResult-BRgIgxWh.es-DiE7kPNX.js +0 -136
- package/dist/app/assets/ThemeEditorPage-D6t7w9Js.js +0 -1
- package/dist/app/assets/index-ByieDZlo.js +0 -23
- package/dist/app/assets/index-CCNqF0-G.js +0 -1
- package/dist/app/assets/index-CKmZ4HG7.js +0 -1322
- package/dist/app/assets/index-CdExa3Pk.js +0 -1
- package/dist/app/assets/index-TiWjmdoW.js +0 -450
- package/dist/app/assets/index.es-iUV0gCYk.js +0 -111
- 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]*[([{<]
|
|
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
|
|
13470
|
-
|
|
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
|
|
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]*[([{<]
|
|
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.
|
|
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
|
-
|
|
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
|
|
16031
|
+
return trimmed;
|
|
15800
16032
|
}
|
|
15801
|
-
return
|
|
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
|
-
|
|
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 (!
|
|
16522
|
+
if (!isDocumentModelPath(modelPath))
|
|
16243
16523
|
return;
|
|
16244
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23304
|
-
|
|
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(
|
|
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
|
-
|
|
23377
|
-
|
|
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
|
-
|
|
23381
|
-
|
|
23382
|
-
|
|
23383
|
-
|
|
23384
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
24038
|
-
import * as
|
|
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
|
|
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
|
|
24522
|
+
let path3 = url.toString();
|
|
24051
24523
|
if (url.protocol == "file:") {
|
|
24052
|
-
|
|
24524
|
+
path3 = fileURLToPath(url);
|
|
24053
24525
|
}
|
|
24054
|
-
return
|
|
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
|
|
24081
|
-
import * as
|
|
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((
|
|
25047
|
+
return new Promise((resolve3, reject2) => {
|
|
24328
25048
|
pendingRpc.set(requestId, {
|
|
24329
|
-
resolve: (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 &&
|
|
25169
|
+
if (job.replacement && path3.resolve(filePath) === path3.resolve(job.packagePath, job.replacement.modelPath)) {
|
|
24450
25170
|
return job.replacement.source;
|
|
24451
25171
|
}
|
|
24452
|
-
return
|
|
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 =
|
|
24503
|
-
const contents = await
|
|
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) =>
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
|
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 =
|
|
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 ?
|
|
25505
|
+
modelInfo: finalModelDef ? modelInfoOf(finalModelDef) : undefined,
|
|
24785
25506
|
sourceInfos: finalSourceInfos,
|
|
24786
25507
|
sources: finalSources,
|
|
24787
25508
|
queries: finalQueries,
|