@malloy-publisher/server 0.10.6 → 0.11.0

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 (34) hide show
  1. package/dist/app/api-doc.yaml +956 -125
  2. package/dist/app/assets/{EnvironmentPage-B9Q9ZYYM.js → EnvironmentPage-Dfn26tY2.js} +1 -1
  3. package/dist/app/assets/{HomePage-CVbXvH7c.js → HomePage-CNuMPINA.js} +1 -1
  4. package/dist/app/assets/MainPage-DWjs4_e3.js +1 -0
  5. package/dist/app/assets/ModelPage-GZ6hXGgw.js +2 -0
  6. package/dist/app/assets/PackagePage-_5vFWxqm.js +1 -0
  7. package/dist/app/assets/PackageSplatRoute-CTxtXKZO.js +2 -0
  8. package/dist/app/assets/{RenderedResult-B_sW7neX.es-CFeRQyYD.js → RenderedResult-r3dg2xOJ.es-t-MnqNaH.js} +3 -3
  9. package/dist/app/assets/{RouteError-BYb6Nj48.js → RouteError-y4xkvszw.js} +1 -1
  10. package/dist/app/assets/{ThemeEditorPage-CYe_2MfI.js → ThemeEditorPage-BffGFYK5.js} +1 -1
  11. package/dist/app/assets/ToggleButtonGroup-BfTJg9cJ.js +1 -0
  12. package/dist/app/assets/documentRoutes-DnpGGXs4.js +1 -0
  13. package/dist/app/assets/editPageMounted-efTmRdGK.js +2 -0
  14. package/dist/app/assets/{index-DEj7hnfZ.js → index-CG6r_Zwr.js} +1 -1
  15. package/dist/app/assets/{index-DWhQwvJR.js → index-CW23I6Cr.js} +1 -1
  16. package/dist/app/assets/index-D8JKtZm5.js +483 -0
  17. package/dist/app/assets/{index-3fArIYYK.js → index-g1sInkiO.js} +1 -1
  18. package/dist/app/assets/{index.es-CMsNTB-h.js → index.es-AXVff-Xr.js} +5 -5
  19. package/dist/app/assets/usePackageLinkNavigate-DpGlRKnc.js +1 -0
  20. package/dist/app/assets/usePackageRoute-BUdFd5dP.js +1 -0
  21. package/dist/app/index.html +1 -1
  22. package/dist/cpufeatures-gknxfsk5.node +0 -0
  23. package/dist/instrumentation.mjs +1 -1
  24. package/dist/package_load_worker.mjs +232 -190
  25. package/dist/runtime/publisher.js +58 -1
  26. package/dist/server.mjs +4518 -1138
  27. package/package.json +1 -1
  28. package/dist/app/assets/MainPage-D1Fj4Wy1.js +0 -1
  29. package/dist/app/assets/ModelPage-DmYocpWT.js +0 -2
  30. package/dist/app/assets/PackagePage-4TwX_ajC.js +0 -1
  31. package/dist/app/assets/ToggleButtonGroup-b325mGmL.js +0 -1
  32. package/dist/app/assets/documentRoutes-D_C8snHV.js +0 -1
  33. package/dist/app/assets/editPageMounted-1X99LP7v.js +0 -2
  34. package/dist/app/assets/index-CGKV9Dxi.js +0 -483
@@ -0,0 +1 @@
1
+ import{p,r as u}from"./index-D8JKtZm5.js";import{w as c}from"./usePackageRoute-BUdFd5dP.js";function n(t,a,r){const s=p();return u.useCallback((o,i)=>s(c(o,t,a,r),i),[s,t,a,r])}export{n as u};
@@ -0,0 +1 @@
1
+ import{C as f,a9 as m}from"./index-D8JKtZm5.js";const p=/^(latest|[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?)$/,$="latest",l=e=>p.test(e),g=e=>{const n=e.indexOf("/"),o=n===-1?e:e.slice(0,n);if(l(o))return{segment:o,rest:n===-1?"":e.slice(n+1)}},h=e=>e===void 0||e===$?void 0:e,I=(e,n,o)=>`/${encodeURIComponent(e)}/${encodeURIComponent(n)}${o===void 0?"":`/${encodeURIComponent(o)}`}`,E=(e,n,o,t)=>{if(t===void 0)return e;const i=[`/${encodeURIComponent(n)}/${encodeURIComponent(o)}`,`/${n}/${o}`];for(const s of i){if(e!==s&&!e.startsWith(`${s}/`))continue;const r=e.slice(s.length).replace(/^\//,"");return`${I(n,o,t)}${r===""?"":`/${r}`}`}return e};function R(e={}){const n=f(),o=n.environmentName,t=n.packageName,i=n["*"],s=i===void 0?void 0:g(i),{versions:r,isPending:d,isError:v,refetch:u}=m(o??"",t??"",{enabled:e.always===!0||s!==void 0}),a=r.length>0,c=s&&a?s:void 0;return{environmentName:o,packageName:t,versionSegment:c?.segment,versionId:h(c?.segment),path:c?c.rest:i,versioned:a,deciding:s!==void 0&&d,undecided:s!==void 0&&v,retry:u,writable:!a&&!d&&!v}}export{$ as L,I as p,R as u,h as v,E as w};
@@ -16,7 +16,7 @@ SPDX-License-Identifier: MIT
16
16
  href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
17
17
  />
18
18
  <title>Malloy Publisher</title>
19
- <script type="module" crossorigin src="/assets/index-CGKV9Dxi.js"></script>
19
+ <script type="module" crossorigin src="/assets/index-D8JKtZm5.js"></script>
20
20
  <link rel="stylesheet" crossorigin href="/assets/index-DUqldpo0.css">
21
21
  </head>
22
22
  <body>
Binary file
@@ -164831,7 +164831,7 @@ var init_telemetry = __esm(() => {
164831
164831
  });
164832
164832
 
164833
164833
  // package.json
164834
- var version = "0.10.6";
164834
+ var version = "0.11.0";
164835
164835
  var init_package = () => {};
164836
164836
 
164837
164837
  // src/version.ts
@@ -12133,6 +12133,14 @@ function logInternalFailure(summary, error, level = "error") {
12133
12133
  }
12134
12134
  });
12135
12135
  }
12136
+ function refuseVersionDelete(packageName, versionId, inService, latest, activeCount) {
12137
+ if (latest === versionId) {
12138
+ throw new PackageVersionError("VERSION_IS_LATEST", `Version ${versionId} is the latest version of package ${packageName}; make another version latest before deleting it.`);
12139
+ }
12140
+ if (inService && activeCount <= 1) {
12141
+ throw new PackageVersionError("VERSION_IS_LAST_ACTIVE", `Version ${versionId} is the last version of package ${packageName} in service; delete its archived versions first, or delete the package.`);
12142
+ }
12143
+ }
12136
12144
  function filesystemAccessFailure(error) {
12137
12145
  let current = error;
12138
12146
  for (let depth = 0;depth < 8 && current instanceof Error; depth++) {
@@ -12202,7 +12210,9 @@ function internalErrorToHttpError(error, { log = true, onLogged } = {}) {
12202
12210
  logInternal("Filesystem access refused", error, "warn");
12203
12211
  return httpError(500, `The server cannot access a path it needs (${describeFilesystemAccessFailure(access)}). ` + `Give the user the server runs as access to it.`);
12204
12212
  }
12205
- if (error instanceof BadRequestError) {
12213
+ if (error instanceof PackageVersionError) {
12214
+ return httpError(error.status, error.message, error.reason);
12215
+ } else if (error instanceof BadRequestError) {
12206
12216
  return httpError(400, error.message);
12207
12217
  } else if (error instanceof ServerConfigurationError) {
12208
12218
  logInternal("Server configuration error", error, "warn");
@@ -12260,6 +12270,8 @@ function internalErrorToHttpError(error, { log = true, onLogged } = {}) {
12260
12270
  return httpError(422, error.message);
12261
12271
  } else if (error instanceof ModelCompilationError) {
12262
12272
  return httpError(424, error.message);
12273
+ } else if (error instanceof VersionFilesMissingError) {
12274
+ return httpError(424, error.message);
12263
12275
  } else if (error instanceof PackageManifestError) {
12264
12276
  return httpError(424, error.message);
12265
12277
  } else if (error instanceof ConnectionError) {
@@ -12311,12 +12323,36 @@ function toQueryTextProblem(problem) {
12311
12323
  ...problem.at ? { at: { range: problem.at.range } } : {}
12312
12324
  };
12313
12325
  }
12314
- var GENERIC_INTERNAL_MESSAGE = "Internal server error.", GENERIC_UPSTREAM_MESSAGE = "Upstream connection error.", GENERIC_UNREACHABLE_MESSAGE = "The database connection is down: the database could not be reached, so the query did not run.", GENERIC_AUTH_MESSAGE = "The database rejected the connection's credentials. Check the connection's user, password, key or token.", MAX_LOGGED_DETAIL_CHARS = 2000, FILESYSTEM_ACCESS_DESCRIPTIONS, POSTGRES_STATEMENT_SQLSTATES, POSTGRES_UNDEFINED_TABLE = "42P01", POSTGRES_RELATION_MISSING, NotImplementedError, BadRequestError, InvalidArgumentError, CompileRefusedError, InvalidModelPathError, RenderTagRefusedError, UnparseableTextError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, SkillNotFoundError, AgentNotFoundError, ConflictError, ConnectionNotFoundError, TableNotFoundError, ConnectionError, ConnectionFailedError, UnconfiguredConnectionError, ConnectionPoolExhaustedError, DestinationNotFoundError, StorageDestinationNotFoundError, StorageCatalogNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, PackageManifestError, MaterializationEligibilityError, ChainedUpstreamMissingError, ChainedShapeNotCarriedError, PublisherConfigError, ServerConfigurationError, FrozenConfigError, AccessDeniedError, QueryCompileError, NotQueryableError, OffSurfaceError, MaterializationNotFoundError, MaterializationConflictError, WriteConflictError, WriteRolledBackError, WriteRollbackFailedError, WriteVerifyError, InvalidStateTransitionError, ServiceUnavailableError, PackageAdmissionRefusedError, PayloadTooLargeError, ResponseUnserializableError, QueryTimeoutError;
12326
+ var GENERIC_INTERNAL_MESSAGE = "Internal server error.", GENERIC_UPSTREAM_MESSAGE = "Upstream connection error.", GENERIC_UNREACHABLE_MESSAGE = "The database connection is down: the database could not be reached, so the query did not run.", GENERIC_AUTH_MESSAGE = "The database rejected the connection's credentials. Check the connection's user, password, key or token.", MAX_LOGGED_DETAIL_CHARS = 2000, PACKAGE_VERSION_ERROR_STATUS, PackageVersionError, FILESYSTEM_ACCESS_DESCRIPTIONS, POSTGRES_STATEMENT_SQLSTATES, POSTGRES_UNDEFINED_TABLE = "42P01", POSTGRES_RELATION_MISSING, NotImplementedError, BadRequestError, InvalidArgumentError, CompileRefusedError, InvalidModelPathError, RenderTagRefusedError, UnparseableTextError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, SkillNotFoundError, AgentNotFoundError, ConflictError, ConnectionNotFoundError, TableNotFoundError, ConnectionError, ConnectionFailedError, UnconfiguredConnectionError, ConnectionPoolExhaustedError, DestinationNotFoundError, StorageDestinationNotFoundError, StorageCatalogNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, VersionFilesMissingError, PackageManifestError, MaterializationEligibilityError, ChainedUpstreamMissingError, ChainedShapeNotCarriedError, PublisherConfigError, ServerConfigurationError, FrozenConfigError, AccessDeniedError, QueryCompileError, NotQueryableError, OffSurfaceError, MaterializationNotFoundError, MaterializationConflictError, WriteConflictError, WriteRolledBackError, WriteRollbackFailedError, WriteVerifyError, InvalidStateTransitionError, ServiceUnavailableError, PackageAdmissionRefusedError, PayloadTooLargeError, ResponseUnserializableError, QueryTimeoutError;
12315
12327
  var init_errors = __esm(() => {
12316
12328
  init_constants();
12317
12329
  init_logger();
12318
12330
  init_driver_errors();
12319
12331
  init_pg_helpers();
12332
+ PACKAGE_VERSION_ERROR_STATUS = {
12333
+ MANIFEST_VERSION_MISSING: 400,
12334
+ MANIFEST_VERSION_INVALID: 400,
12335
+ VERSION_ID_INVALID: 400,
12336
+ VERSION_NOT_FOUND: 404,
12337
+ VERSION_CONFLICT: 409,
12338
+ PACKAGE_IS_VERSIONED: 409,
12339
+ VERSION_IS_LATEST: 409,
12340
+ VERSION_IS_LAST_ACTIVE: 409,
12341
+ VERSION_BUILDING: 409,
12342
+ VERSION_NOT_LATEST: 400,
12343
+ VERSION_ARCHIVED: 410
12344
+ };
12345
+ PackageVersionError = class PackageVersionError extends Error {
12346
+ reason;
12347
+ constructor(reason, message) {
12348
+ super(message);
12349
+ this.reason = reason;
12350
+ this.name = "PackageVersionError";
12351
+ }
12352
+ get status() {
12353
+ return PACKAGE_VERSION_ERROR_STATUS[this.reason];
12354
+ }
12355
+ };
12320
12356
  FILESYSTEM_ACCESS_DESCRIPTIONS = {
12321
12357
  EACCES: "permission denied",
12322
12358
  EPERM: "operation not permitted",
@@ -12471,6 +12507,12 @@ var init_errors = __esm(() => {
12471
12507
  super(error.message);
12472
12508
  }
12473
12509
  };
12510
+ VersionFilesMissingError = class VersionFilesMissingError extends Error {
12511
+ constructor(message) {
12512
+ super(message);
12513
+ this.name = "VersionFilesMissingError";
12514
+ }
12515
+ };
12474
12516
  PackageManifestError = class PackageManifestError extends Error {
12475
12517
  constructor(message) {
12476
12518
  super(message);
@@ -14203,6 +14245,189 @@ var init_package_revision = __esm(() => {
14203
14245
  MANIFEST_AGENTS_MAX_BYTES = 1024 * 1024;
14204
14246
  });
14205
14247
 
14248
+ // src/service/package_manifest.ts
14249
+ function parsePackageScope(raw) {
14250
+ if (raw === undefined || raw === null) {
14251
+ return "package";
14252
+ }
14253
+ if (PACKAGE_SCOPES.includes(raw)) {
14254
+ return raw;
14255
+ }
14256
+ throw new PackageManifestError(`Invalid "scope" in the package manifest: ${JSON.stringify(raw)}. ` + `Expected "version" or "package" (default "package").`);
14257
+ }
14258
+ function resolvePackageScope(rootRaw, materializationRaw) {
14259
+ const envelopeRaw = materializationRaw && typeof materializationRaw === "object" ? materializationRaw.scope : undefined;
14260
+ const rootDeclared = rootRaw !== undefined && rootRaw !== null;
14261
+ const envelopeDeclared = envelopeRaw !== undefined && envelopeRaw !== null;
14262
+ const rootScope = parsePackageScope(rootRaw);
14263
+ const envelopeScope = parsePackageScope(envelopeRaw);
14264
+ if (rootDeclared && envelopeDeclared) {
14265
+ if (rootScope !== envelopeScope) {
14266
+ throw new PackageManifestError(`Conflicting "scope" in publisher.json: root "${rootScope}" vs ` + `"materialization.scope" "${envelopeScope}". The package cannot ` + `load until they agree. Edit "materialization": { "scope": ... } ` + `to the value you want and delete the root-level "scope" (the ` + `server rewrites both homes on its next manifest write).`);
14267
+ }
14268
+ return { scope: envelopeScope, warnings: [] };
14269
+ }
14270
+ if (rootDeclared) {
14271
+ return { scope: rootScope, warnings: [SCOPE_ROOT_DEPRECATION] };
14272
+ }
14273
+ return { scope: envelopeScope, warnings: [] };
14274
+ }
14275
+ function resolvePackageQueryMetadata(rootRaw, materializationRaw) {
14276
+ const envelopeRaw = materializationRaw && typeof materializationRaw === "object" ? materializationRaw.queryMetadata : undefined;
14277
+ const rootDeclared = rootRaw !== undefined && rootRaw !== null;
14278
+ const envelopeDeclared = envelopeRaw !== undefined && envelopeRaw !== null;
14279
+ if (rootDeclared && envelopeDeclared) {
14280
+ if (sameQueryMetadataBag(rootRaw, envelopeRaw)) {
14281
+ return { queryMetadata: rootRaw, home: "root", warnings: [] };
14282
+ }
14283
+ return {
14284
+ queryMetadata: rootRaw,
14285
+ home: "root",
14286
+ warnings: [QUERY_METADATA_CONFLICT]
14287
+ };
14288
+ }
14289
+ if (envelopeDeclared) {
14290
+ return {
14291
+ queryMetadata: envelopeRaw,
14292
+ home: "envelope",
14293
+ warnings: [QUERY_METADATA_ENVELOPE_DEPRECATION]
14294
+ };
14295
+ }
14296
+ return { queryMetadata: rootRaw, home: "root", warnings: [] };
14297
+ }
14298
+ function sameQueryMetadataBag(a, b) {
14299
+ return keySortedJson(a) === keySortedJson(b);
14300
+ }
14301
+ function keySortedJson(value) {
14302
+ if (value === null || typeof value !== "object") {
14303
+ return JSON.stringify(value) ?? "undefined";
14304
+ }
14305
+ if (Array.isArray(value)) {
14306
+ return `[${value.map(keySortedJson).join(",")}]`;
14307
+ }
14308
+ const entries = Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
14309
+ return `{${entries.map(([name, nested]) => `${JSON.stringify(name)}:${keySortedJson(nested)}`).join(",")}}`;
14310
+ }
14311
+ function parseFreshness(raw) {
14312
+ if (!raw || typeof raw !== "object") {
14313
+ return null;
14314
+ }
14315
+ const { window: window2, fallback } = raw;
14316
+ const freshness = {};
14317
+ if (typeof window2 === "string") {
14318
+ freshness.window = window2;
14319
+ }
14320
+ if (typeof fallback === "string" && FRESHNESS_FALLBACKS.includes(fallback)) {
14321
+ freshness.fallback = fallback;
14322
+ }
14323
+ return freshness;
14324
+ }
14325
+ function parseQueryMetadata(raw, label = QUERY_METADATA_HOME_LABELS.envelope) {
14326
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
14327
+ return { metadata: null, warnings: [] };
14328
+ }
14329
+ const out = {};
14330
+ const warnings = [];
14331
+ for (const [name, value] of Object.entries(raw)) {
14332
+ if (typeof value === "string") {
14333
+ out[name] = value;
14334
+ } else {
14335
+ warnings.push(`${label}: property '${name}' must be a ` + `string (got ${value === null ? "null" : typeof value}); it is ` + `not attached to any statement`);
14336
+ }
14337
+ }
14338
+ return {
14339
+ metadata: Object.keys(out).length > 0 ? out : null,
14340
+ warnings
14341
+ };
14342
+ }
14343
+ function parsePackageMaterialization(raw) {
14344
+ if (!raw || typeof raw !== "object") {
14345
+ return null;
14346
+ }
14347
+ const { schedule, freshness, queryMetadata } = raw;
14348
+ return {
14349
+ schedule: typeof schedule === "string" ? schedule : null,
14350
+ freshness: parseFreshness(freshness),
14351
+ queryMetadata: parseQueryMetadata(queryMetadata).metadata
14352
+ };
14353
+ }
14354
+ function materializationWithQueryMetadata(parsed, queryMetadataRaw) {
14355
+ const queryMetadata = parseQueryMetadata(queryMetadataRaw).metadata;
14356
+ if (!parsed && !queryMetadata)
14357
+ return null;
14358
+ return {
14359
+ schedule: parsed?.schedule ?? null,
14360
+ freshness: parsed?.freshness ?? null,
14361
+ queryMetadata
14362
+ };
14363
+ }
14364
+ function queryMetadataParseWarnings(raw, home = "root") {
14365
+ return parseQueryMetadata(raw, QUERY_METADATA_HOME_LABELS[home]).warnings;
14366
+ }
14367
+ function resolveExplores(input) {
14368
+ const { declaredExplores, declaredQueryableSources, modelPaths } = input;
14369
+ const reportPaths = input.reportPaths ?? new Set;
14370
+ const hasIndexModel = modelPaths.includes(INDEX_MODEL_NAME);
14371
+ const warnings = [];
14372
+ if (declaredExplores !== undefined && !(Array.isArray(declaredExplores) && declaredExplores.every((entry) => typeof entry === "string"))) {
14373
+ throw new PackageManifestError(`Invalid "explores" in publisher.json: it must be a list of file ` + `names, but is ${JSON.stringify(declaredExplores)}. The package ` + `was not loaded. Fix: delete "explores" and add an ` + `${INDEX_MODEL_NAME}.`);
14374
+ }
14375
+ const declared = Array.isArray(declaredExplores) ? declaredExplores.map((entry) => normalizeModelPath(entry)) : undefined;
14376
+ if (declaredQueryableSources !== undefined && declaredQueryableSources !== "all") {
14377
+ warnings.push(QUERYABLE_SOURCES_DEPRECATION);
14378
+ }
14379
+ if (declared !== undefined) {
14380
+ if (declared.length === 0) {
14381
+ warnings.push(hasIndexModel ? EXPLORES_EMPTY_SUPPRESSES_CONVENTION : EXPLORES_EMPTY_IS_UNCURATED);
14382
+ } else {
14383
+ warnings.push(exploresDeprecation(declared, hasIndexModel, reportPaths));
14384
+ }
14385
+ if (hasIndexModel && declared.length > 0 && !declared.includes(INDEX_MODEL_NAME)) {
14386
+ warnings.push(EXPLORES_OMITS_INDEX_MODEL);
14387
+ }
14388
+ return { explores: declared, warnings };
14389
+ }
14390
+ if (!hasIndexModel) {
14391
+ for (const modelPath of modelPaths) {
14392
+ if (!modelPath.includes("/") && modelPath.toLowerCase() === INDEX_MODEL_NAME) {
14393
+ warnings.push(`${modelPath} is ignored: only a root file named exactly ` + `${INDEX_MODEL_NAME} decides what is published. Fix: rename ` + `it to ${INDEX_MODEL_NAME}.`);
14394
+ }
14395
+ }
14396
+ return { explores: undefined, warnings };
14397
+ }
14398
+ return { explores: [INDEX_MODEL_NAME], warnings };
14399
+ }
14400
+ function listFiles(files) {
14401
+ return files.length <= 1 ? files[0] ?? "" : `${files.slice(0, -1).join(", ")} and ${files[files.length - 1]}`;
14402
+ }
14403
+ function exploresDeprecation(declared, hasIndexModel, reportPaths) {
14404
+ const lead = `"explores" in publisher.json is deprecated.`;
14405
+ const files = declared.filter((entry) => entry !== INDEX_MODEL_NAME && !reportPaths.has(entry));
14406
+ if (files.length > 0) {
14407
+ return hasIndexModel ? `${lead} Fix: import ${listFiles(files)} into ${INDEX_MODEL_NAME}, ` + `export the sources you publish from it, then delete "explores".` : `${lead} Fix: add an ${INDEX_MODEL_NAME} at the package root that ` + `imports ${listFiles(files)} and exports the sources you want to ` + `publish, then delete "explores".`;
14408
+ }
14409
+ if (declared.includes(INDEX_MODEL_NAME)) {
14410
+ return `${lead} ${INDEX_MODEL_NAME} already publishes the same thing. ` + `Fix: delete "explores".`;
14411
+ }
14412
+ return hasIndexModel ? `${lead} Fix: delete "explores", so ${INDEX_MODEL_NAME} decides what is published.` : `${lead} Fix: add an ${INDEX_MODEL_NAME} at the package root that ` + `exports the sources you want to publish, then delete "explores".`;
14413
+ }
14414
+ var FRESHNESS_FALLBACKS, PACKAGE_SCOPES, SCOPE_ROOT_DEPRECATION, QUERY_METADATA_HOME_LABELS, QUERY_METADATA_ENVELOPE_DEPRECATION, QUERY_METADATA_CONFLICT, EXPLORES_OMITS_INDEX_MODEL, EXPLORES_EMPTY_SUPPRESSES_CONVENTION, EXPLORES_EMPTY_IS_UNCURATED = `"explores": [] in publisher.json does nothing. Fix: delete it.`, QUERYABLE_SOURCES_DEPRECATION = `"queryableSources" in publisher.json does nothing. Fix: delete it.`;
14415
+ var init_package_manifest = __esm(() => {
14416
+ init_constants();
14417
+ init_errors();
14418
+ FRESHNESS_FALLBACKS = ["live", "stale_ok", "fail"];
14419
+ PACKAGE_SCOPES = ["version", "package"];
14420
+ SCOPE_ROOT_DEPRECATION = `"scope" at the manifest root is deprecated: declare it as ` + `"materialization": { "scope": ... } alongside the other build knobs. The ` + `root form still works and will be removed in a future release; until then ` + `the server keeps both homes in sync when it writes the manifest, so an ` + `older publisher still reads the right value.`;
14421
+ QUERY_METADATA_HOME_LABELS = {
14422
+ root: "queryMetadata",
14423
+ envelope: "materialization.queryMetadata"
14424
+ };
14425
+ QUERY_METADATA_ENVELOPE_DEPRECATION = `"queryMetadata" inside "materialization" is deprecated: declare it at the ` + `manifest root instead. It is not a build setting — the properties ride ` + `every statement the package's sources issue, including served queries. The ` + `enveloped form still works and will be removed in a future release; until ` + `then the server keeps both homes in sync when it writes the manifest, so an ` + `older publisher still reads the right value.`;
14426
+ QUERY_METADATA_CONFLICT = `Conflicting "queryMetadata" in publisher.json: the manifest root and ` + `"materialization.queryMetadata" declare different bags, and the root wins. ` + `Delete "materialization": { "queryMetadata": ... } — the server rewrites ` + `both homes on its next manifest write, so an older publisher still reads ` + `the right value.`;
14427
+ EXPLORES_OMITS_INDEX_MODEL = `${INDEX_MODEL_NAME} is ignored because "explores" in publisher.json ` + `doesn't list it. Fix: delete "explores" to publish what ` + `${INDEX_MODEL_NAME} exports, or rename ${INDEX_MODEL_NAME} (and any import ` + `of it) if it isn't meant to decide what is published.`;
14428
+ EXPLORES_EMPTY_SUPPRESSES_CONVENTION = `"explores" in publisher.json is deprecated. Here it stops ` + `${INDEX_MODEL_NAME} from limiting what this package publishes. Fix: to ` + `publish everything, rename ${INDEX_MODEL_NAME}, point any import of it at ` + `the new name, then delete "explores".`;
14429
+ });
14430
+
14206
14431
  // src/service/package_retrieval.ts
14207
14432
  import { createHash as createHash2 } from "crypto";
14208
14433
  import * as fs2 from "fs";
@@ -23652,6 +23877,11 @@ init_gate_classification();
23652
23877
  init_gate_dimension();
23653
23878
  init_model_info();
23654
23879
  init_package_revision();
23880
+ init_package_manifest();
23881
+ init_package_retrieval();
23882
+ init_source_extraction();
23883
+ init_given();
23884
+ init_utils();
23655
23885
  var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
23656
23886
  import {
23657
23887
  contextOverlay,
@@ -23666,194 +23896,6 @@ import { AsyncLocalStorage } from "node:async_hooks";
23666
23896
  import { parentPort, threadId, workerData } from "node:worker_threads";
23667
23897
  import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
23668
23898
 
23669
- // src/service/package_manifest.ts
23670
- init_constants();
23671
- init_errors();
23672
- var FRESHNESS_FALLBACKS = ["live", "stale_ok", "fail"];
23673
- var PACKAGE_SCOPES = ["version", "package"];
23674
- function parsePackageScope(raw) {
23675
- if (raw === undefined || raw === null) {
23676
- return "package";
23677
- }
23678
- if (PACKAGE_SCOPES.includes(raw)) {
23679
- return raw;
23680
- }
23681
- throw new PackageManifestError(`Invalid "scope" in the package manifest: ${JSON.stringify(raw)}. ` + `Expected "version" or "package" (default "package").`);
23682
- }
23683
- function resolvePackageScope(rootRaw, materializationRaw) {
23684
- const envelopeRaw = materializationRaw && typeof materializationRaw === "object" ? materializationRaw.scope : undefined;
23685
- const rootDeclared = rootRaw !== undefined && rootRaw !== null;
23686
- const envelopeDeclared = envelopeRaw !== undefined && envelopeRaw !== null;
23687
- const rootScope = parsePackageScope(rootRaw);
23688
- const envelopeScope = parsePackageScope(envelopeRaw);
23689
- if (rootDeclared && envelopeDeclared) {
23690
- if (rootScope !== envelopeScope) {
23691
- throw new PackageManifestError(`Conflicting "scope" in publisher.json: root "${rootScope}" vs ` + `"materialization.scope" "${envelopeScope}". The package cannot ` + `load until they agree. Edit "materialization": { "scope": ... } ` + `to the value you want and delete the root-level "scope" (the ` + `server rewrites both homes on its next manifest write).`);
23692
- }
23693
- return { scope: envelopeScope, warnings: [] };
23694
- }
23695
- if (rootDeclared) {
23696
- return { scope: rootScope, warnings: [SCOPE_ROOT_DEPRECATION] };
23697
- }
23698
- return { scope: envelopeScope, warnings: [] };
23699
- }
23700
- var SCOPE_ROOT_DEPRECATION = `"scope" at the manifest root is deprecated: declare it as ` + `"materialization": { "scope": ... } alongside the other build knobs. The ` + `root form still works and will be removed in a future release; until then ` + `the server keeps both homes in sync when it writes the manifest, so an ` + `older publisher still reads the right value.`;
23701
- function resolvePackageQueryMetadata(rootRaw, materializationRaw) {
23702
- const envelopeRaw = materializationRaw && typeof materializationRaw === "object" ? materializationRaw.queryMetadata : undefined;
23703
- const rootDeclared = rootRaw !== undefined && rootRaw !== null;
23704
- const envelopeDeclared = envelopeRaw !== undefined && envelopeRaw !== null;
23705
- if (rootDeclared && envelopeDeclared) {
23706
- if (sameQueryMetadataBag(rootRaw, envelopeRaw)) {
23707
- return { queryMetadata: rootRaw, home: "root", warnings: [] };
23708
- }
23709
- return {
23710
- queryMetadata: rootRaw,
23711
- home: "root",
23712
- warnings: [QUERY_METADATA_CONFLICT]
23713
- };
23714
- }
23715
- if (envelopeDeclared) {
23716
- return {
23717
- queryMetadata: envelopeRaw,
23718
- home: "envelope",
23719
- warnings: [QUERY_METADATA_ENVELOPE_DEPRECATION]
23720
- };
23721
- }
23722
- return { queryMetadata: rootRaw, home: "root", warnings: [] };
23723
- }
23724
- function sameQueryMetadataBag(a, b) {
23725
- return keySortedJson(a) === keySortedJson(b);
23726
- }
23727
- function keySortedJson(value) {
23728
- if (value === null || typeof value !== "object") {
23729
- return JSON.stringify(value) ?? "undefined";
23730
- }
23731
- if (Array.isArray(value)) {
23732
- return `[${value.map(keySortedJson).join(",")}]`;
23733
- }
23734
- const entries = Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
23735
- return `{${entries.map(([name, nested]) => `${JSON.stringify(name)}:${keySortedJson(nested)}`).join(",")}}`;
23736
- }
23737
- var QUERY_METADATA_HOME_LABELS = {
23738
- root: "queryMetadata",
23739
- envelope: "materialization.queryMetadata"
23740
- };
23741
- var QUERY_METADATA_ENVELOPE_DEPRECATION = `"queryMetadata" inside "materialization" is deprecated: declare it at the ` + `manifest root instead. It is not a build setting — the properties ride ` + `every statement the package's sources issue, including served queries. The ` + `enveloped form still works and will be removed in a future release; until ` + `then the server keeps both homes in sync when it writes the manifest, so an ` + `older publisher still reads the right value.`;
23742
- var QUERY_METADATA_CONFLICT = `Conflicting "queryMetadata" in publisher.json: the manifest root and ` + `"materialization.queryMetadata" declare different bags, and the root wins. ` + `Delete "materialization": { "queryMetadata": ... } — the server rewrites ` + `both homes on its next manifest write, so an older publisher still reads ` + `the right value.`;
23743
- function parseFreshness(raw) {
23744
- if (!raw || typeof raw !== "object") {
23745
- return null;
23746
- }
23747
- const { window: window2, fallback } = raw;
23748
- const freshness = {};
23749
- if (typeof window2 === "string") {
23750
- freshness.window = window2;
23751
- }
23752
- if (typeof fallback === "string" && FRESHNESS_FALLBACKS.includes(fallback)) {
23753
- freshness.fallback = fallback;
23754
- }
23755
- return freshness;
23756
- }
23757
- function parseQueryMetadata(raw, label = QUERY_METADATA_HOME_LABELS.envelope) {
23758
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
23759
- return { metadata: null, warnings: [] };
23760
- }
23761
- const out = {};
23762
- const warnings = [];
23763
- for (const [name, value] of Object.entries(raw)) {
23764
- if (typeof value === "string") {
23765
- out[name] = value;
23766
- } else {
23767
- warnings.push(`${label}: property '${name}' must be a ` + `string (got ${value === null ? "null" : typeof value}); it is ` + `not attached to any statement`);
23768
- }
23769
- }
23770
- return {
23771
- metadata: Object.keys(out).length > 0 ? out : null,
23772
- warnings
23773
- };
23774
- }
23775
- function parsePackageMaterialization(raw) {
23776
- if (!raw || typeof raw !== "object") {
23777
- return null;
23778
- }
23779
- const { schedule, freshness, queryMetadata } = raw;
23780
- return {
23781
- schedule: typeof schedule === "string" ? schedule : null,
23782
- freshness: parseFreshness(freshness),
23783
- queryMetadata: parseQueryMetadata(queryMetadata).metadata
23784
- };
23785
- }
23786
- function materializationWithQueryMetadata(parsed, queryMetadataRaw) {
23787
- const queryMetadata = parseQueryMetadata(queryMetadataRaw).metadata;
23788
- if (!parsed && !queryMetadata)
23789
- return null;
23790
- return {
23791
- schedule: parsed?.schedule ?? null,
23792
- freshness: parsed?.freshness ?? null,
23793
- queryMetadata
23794
- };
23795
- }
23796
- function queryMetadataParseWarnings(raw, home = "root") {
23797
- return parseQueryMetadata(raw, QUERY_METADATA_HOME_LABELS[home]).warnings;
23798
- }
23799
- function resolveExplores(input) {
23800
- const { declaredExplores, declaredQueryableSources, modelPaths } = input;
23801
- const reportPaths = input.reportPaths ?? new Set;
23802
- const hasIndexModel = modelPaths.includes(INDEX_MODEL_NAME);
23803
- const warnings = [];
23804
- if (declaredExplores !== undefined && !(Array.isArray(declaredExplores) && declaredExplores.every((entry) => typeof entry === "string"))) {
23805
- throw new PackageManifestError(`Invalid "explores" in publisher.json: it must be a list of file ` + `names, but is ${JSON.stringify(declaredExplores)}. The package ` + `was not loaded. Fix: delete "explores" and add an ` + `${INDEX_MODEL_NAME}.`);
23806
- }
23807
- const declared = Array.isArray(declaredExplores) ? declaredExplores.map((entry) => normalizeModelPath(entry)) : undefined;
23808
- if (declaredQueryableSources !== undefined && declaredQueryableSources !== "all") {
23809
- warnings.push(QUERYABLE_SOURCES_DEPRECATION);
23810
- }
23811
- if (declared !== undefined) {
23812
- if (declared.length === 0) {
23813
- warnings.push(hasIndexModel ? EXPLORES_EMPTY_SUPPRESSES_CONVENTION : EXPLORES_EMPTY_IS_UNCURATED);
23814
- } else {
23815
- warnings.push(exploresDeprecation(declared, hasIndexModel, reportPaths));
23816
- }
23817
- if (hasIndexModel && declared.length > 0 && !declared.includes(INDEX_MODEL_NAME)) {
23818
- warnings.push(EXPLORES_OMITS_INDEX_MODEL);
23819
- }
23820
- return { explores: declared, warnings };
23821
- }
23822
- if (!hasIndexModel) {
23823
- for (const modelPath of modelPaths) {
23824
- if (!modelPath.includes("/") && modelPath.toLowerCase() === INDEX_MODEL_NAME) {
23825
- warnings.push(`${modelPath} is ignored: only a root file named exactly ` + `${INDEX_MODEL_NAME} decides what is published. Fix: rename ` + `it to ${INDEX_MODEL_NAME}.`);
23826
- }
23827
- }
23828
- return { explores: undefined, warnings };
23829
- }
23830
- return { explores: [INDEX_MODEL_NAME], warnings };
23831
- }
23832
- function listFiles(files) {
23833
- return files.length <= 1 ? files[0] ?? "" : `${files.slice(0, -1).join(", ")} and ${files[files.length - 1]}`;
23834
- }
23835
- function exploresDeprecation(declared, hasIndexModel, reportPaths) {
23836
- const lead = `"explores" in publisher.json is deprecated.`;
23837
- const files = declared.filter((entry) => entry !== INDEX_MODEL_NAME && !reportPaths.has(entry));
23838
- if (files.length > 0) {
23839
- return hasIndexModel ? `${lead} Fix: import ${listFiles(files)} into ${INDEX_MODEL_NAME}, ` + `export the sources you publish from it, then delete "explores".` : `${lead} Fix: add an ${INDEX_MODEL_NAME} at the package root that ` + `imports ${listFiles(files)} and exports the sources you want to ` + `publish, then delete "explores".`;
23840
- }
23841
- if (declared.includes(INDEX_MODEL_NAME)) {
23842
- return `${lead} ${INDEX_MODEL_NAME} already publishes the same thing. ` + `Fix: delete "explores".`;
23843
- }
23844
- return hasIndexModel ? `${lead} Fix: delete "explores", so ${INDEX_MODEL_NAME} decides what is published.` : `${lead} Fix: add an ${INDEX_MODEL_NAME} at the package root that ` + `exports the sources you want to publish, then delete "explores".`;
23845
- }
23846
- var EXPLORES_OMITS_INDEX_MODEL = `${INDEX_MODEL_NAME} is ignored because "explores" in publisher.json ` + `doesn't list it. Fix: delete "explores" to publish what ` + `${INDEX_MODEL_NAME} exports, or rename ${INDEX_MODEL_NAME} (and any import ` + `of it) if it isn't meant to decide what is published.`;
23847
- var EXPLORES_EMPTY_SUPPRESSES_CONVENTION = `"explores" in publisher.json is deprecated. Here it stops ` + `${INDEX_MODEL_NAME} from limiting what this package publishes. Fix: to ` + `publish everything, rename ${INDEX_MODEL_NAME}, point any import of it at ` + `the new name, then delete "explores".`;
23848
- var EXPLORES_EMPTY_IS_UNCURATED = `"explores": [] in publisher.json does nothing. Fix: delete it.`;
23849
- var QUERYABLE_SOURCES_DEPRECATION = `"queryableSources" in publisher.json does nothing. Fix: delete it.`;
23850
-
23851
- // src/package_load/package_load_worker.ts
23852
- init_package_retrieval();
23853
- init_source_extraction();
23854
- init_given();
23855
- init_utils();
23856
-
23857
23899
  // src/package_load/rpc_wait_accountant.ts
23858
23900
  class RpcWaitAccountant {
23859
23901
  now;
@@ -9,7 +9,9 @@
9
9
  // - Publisher.query(model, malloy, opts?) → Promise<rows[]>
10
10
  // - Publisher.queryFull(model, malloy, opts?) → Promise<MalloyResult> (envelope for <malloy-render>)
11
11
  // opts: { environment?, package?, sourceName?, queryName?, filterParams?,
12
- // bypassFilters?, givens? }. givens is a name→value map bound as
12
+ // bypassFilters?, givens?, versionId? }. versionId names a published
13
+ // version; a page opened with `?versionId=` queries its own
14
+ // package at that version by default. givens is a name→value map bound as
13
15
  // Malloy given: runtime parameters for this query (safe parameterization,
14
16
  // not string interpolation) — see the malloy-html-data-apps skill (reference/runtime.md).
15
17
  // - Publisher.embed(selector, { src, height?, token? })
@@ -68,6 +70,53 @@
68
70
  return bearerToken ? { Authorization: "Bearer " + bearerToken } : {};
69
71
  }
70
72
 
73
+ // --- Published-version pinning -----------------------------------------
74
+ // A page opened at a published version (`?versionId=` in its URL) queries
75
+ // that version, so it keeps answering from the models it shipped with after
76
+ // the package's `latest` moves on. Only when that version is one the
77
+ // package has: a proxy may forward a page's query string unchanged, with a
78
+ // `versionId` of its own, and such a page queries `latest`, as the server
79
+ // serves its files. One read of the package at the page's version tells.
80
+ function pageVersionParam() {
81
+ // Read by hand, not with URLSearchParams, so the runtime also loads where
82
+ // that is not defined. `+` in a query string is a space.
83
+ var match = (location.search || "").match(/[?&]versionId=([^&#]*)/);
84
+ return match && match[1]
85
+ ? safeDecode(match[1].replace(/\+/g, " "))
86
+ : undefined;
87
+ }
88
+ var pageVersion = pageVersionParam();
89
+ var pagePin = null;
90
+ function pinnedVersion() {
91
+ if (!pageVersion || !ctx.environment || !ctx.package) {
92
+ return Promise.resolve(undefined);
93
+ }
94
+ if (!pagePin) {
95
+ pagePin = fetch(
96
+ apiBase +
97
+ "/environments/" +
98
+ encodeURIComponent(ctx.environment) +
99
+ "/packages/" +
100
+ encodeURIComponent(ctx.package) +
101
+ "?versionId=" +
102
+ encodeURIComponent(pageVersion),
103
+ { credentials: "include", headers: authHeaders() },
104
+ )
105
+ .then(function (res) {
106
+ return res.ok ? res.json() : null;
107
+ })
108
+ .then(function (pkg) {
109
+ return pkg && pkg.versionId === pageVersion
110
+ ? pageVersion
111
+ : undefined;
112
+ })
113
+ .catch(function () {
114
+ return undefined;
115
+ });
116
+ }
117
+ return pagePin;
118
+ }
119
+
71
120
  // --- Query helpers -----------------------------------------------------
72
121
  function resolveTarget(opts) {
73
122
  var env = (opts && opts.environment) || ctx.environment;
@@ -106,6 +155,14 @@
106
155
  if (opts.filterParams) body.filterParams = opts.filterParams;
107
156
  if (opts.bypassFilters) body.bypassFilters = true;
108
157
  if (opts.givens) body.givens = opts.givens;
158
+ // The page's own version applies only to its own package; a query aimed
159
+ // elsewhere gets that package's latest unless it names one.
160
+ var versionId =
161
+ opts.versionId ||
162
+ (target.env === ctx.environment && target.pkg === ctx.package
163
+ ? await pinnedVersion()
164
+ : undefined);
165
+ if (versionId) body.versionId = versionId;
109
166
 
110
167
  var headers = Object.assign(
111
168
  { "content-type": "application/json" },