@malloy-publisher/server 0.0.212 → 0.0.214

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.
@@ -2436,6 +2436,14 @@ components:
2436
2436
  URI (gs:// or s3://) of the externally-computed manifest for this package.
2437
2437
  On (re)load the publisher reads it and binds persist references
2438
2438
  (buildId -> physicalTableName). Null = serve live.
2439
+ materialization:
2440
+ oneOf:
2441
+ - $ref: "#/components/schemas/PackageMaterializationConfig"
2442
+ - type: "null"
2443
+ description: |
2444
+ Package-level Malloy Persistence policy declared in
2445
+ malloy-publisher.json. The control plane reads it to drive scheduled
2446
+ re-materialization. Null/absent = no package-level policy.
2439
2447
  manifestBindingStatus:
2440
2448
  type: string
2441
2449
  readOnly: true
@@ -2485,6 +2493,21 @@ components:
2485
2493
  package is compiled; null only when the package declares no persist
2486
2494
  source.
2487
2495
 
2496
+ PackageMaterializationConfig:
2497
+ type: object
2498
+ description: >-
2499
+ Package-level Malloy Persistence policy from malloy-publisher.json's
2500
+ `materialization` block. Surfaced verbatim so the control plane can drive
2501
+ scheduled version-level re-materialization without re-reading the package
2502
+ files.
2503
+ properties:
2504
+ schedule:
2505
+ type: ["string", "null"]
2506
+ description: >-
2507
+ 5-field UNIX cron controlling how often the control plane
2508
+ re-materializes this package's published versions. Null/absent = no
2509
+ scheduled re-materialization (publish / on-demand only).
2510
+
2488
2511
  Model:
2489
2512
  type: object
2490
2513
  description: Malloy model metadata and status information
@@ -115174,7 +115174,6 @@ var init_logger = __esm(() => {
115174
115174
 
115175
115175
  // src/instrumentation.ts
115176
115176
  init_logger();
115177
- var import_api = __toESM(require_src(), 1);
115178
115177
  var import_auto_instrumentations_node = __toESM(require_src59(), 1);
115179
115178
  var import_exporter_logs_otlp_proto = __toESM(require_src66(), 1);
115180
115179
  var import_exporter_prometheus = __toESM(require_src71(), 1);
@@ -115186,6 +115185,15 @@ var import_sdk_node = __toESM(require_src106(), 1);
115186
115185
  var import_sdk_trace_base = __toESM(require_src82(), 1);
115187
115186
  var import_semantic_conventions = __toESM(require_src2(), 1);
115188
115187
  import { monitorEventLoopDelay } from "node:perf_hooks";
115188
+
115189
+ // src/telemetry.ts
115190
+ var import_api = __toESM(require_src(), 1);
115191
+ var METER_NAME = "publisher";
115192
+ function publisherMeter() {
115193
+ return import_api.metrics.getMeter(METER_NAME);
115194
+ }
115195
+
115196
+ // src/instrumentation.ts
115189
115197
  var prometheusExporter = null;
115190
115198
  var sdk = null;
115191
115199
  function getPrometheusMetricsHandler() {
@@ -115243,7 +115251,7 @@ function instrument() {
115243
115251
  }
115244
115252
  }
115245
115253
  instrument();
115246
- var meter = import_api.metrics.getMeter("publisher");
115254
+ var meter = publisherMeter();
115247
115255
  var httpRequestDuration = meter.createHistogram("http_server_request_duration_ms", {
115248
115256
  description: "Duration of HTTP requests in milliseconds",
115249
115257
  unit: "ms",
@@ -11712,6 +11712,15 @@ function unwrapQuotedExpression(body) {
11712
11712
  return expr;
11713
11713
  }
11714
11714
 
11715
+ // src/service/package_manifest.ts
11716
+ function parsePackageMaterialization(raw) {
11717
+ if (!raw || typeof raw !== "object") {
11718
+ return null;
11719
+ }
11720
+ const schedule = raw.schedule;
11721
+ return { schedule: typeof schedule === "string" ? schedule : null };
11722
+ }
11723
+
11715
11724
  // src/service/source_extraction.ts
11716
11725
  import {
11717
11726
  isSourceDef
@@ -12235,7 +12244,8 @@ async function readPackageMetadata(packagePath) {
12235
12244
  description: parsed.description,
12236
12245
  explores: Array.isArray(parsed.explores) ? parsed.explores.map(normalizeModelPath) : undefined,
12237
12246
  queryableSources: parsed.queryableSources === "all" ? "all" : "declared",
12238
- manifestLocation: typeof parsed.manifestLocation === "string" ? parsed.manifestLocation : null
12247
+ manifestLocation: typeof parsed.manifestLocation === "string" ? parsed.manifestLocation : null,
12248
+ materialization: parsePackageMaterialization(parsed.materialization)
12239
12249
  };
12240
12250
  }
12241
12251
  async function listPackageFiles(packagePath) {
package/dist/server.mjs CHANGED
@@ -218980,7 +218980,6 @@ var require_lunr = __commonJS((exports, module) => {
218980
218980
 
218981
218981
  // src/instrumentation.ts
218982
218982
  init_logger();
218983
- var import_api = __toESM(require_src(), 1);
218984
218983
  var import_auto_instrumentations_node = __toESM(require_src59(), 1);
218985
218984
  var import_exporter_logs_otlp_proto = __toESM(require_src66(), 1);
218986
218985
  var import_exporter_prometheus = __toESM(require_src71(), 1);
@@ -218992,6 +218991,15 @@ var import_sdk_node = __toESM(require_src106(), 1);
218992
218991
  var import_sdk_trace_base = __toESM(require_src82(), 1);
218993
218992
  var import_semantic_conventions = __toESM(require_src2(), 1);
218994
218993
  import { monitorEventLoopDelay } from "node:perf_hooks";
218994
+
218995
+ // src/telemetry.ts
218996
+ var import_api = __toESM(require_src(), 1);
218997
+ var METER_NAME = "publisher";
218998
+ function publisherMeter() {
218999
+ return import_api.metrics.getMeter(METER_NAME);
219000
+ }
219001
+
219002
+ // src/instrumentation.ts
218995
219003
  var prometheusExporter = null;
218996
219004
  var sdk = null;
218997
219005
  function getPrometheusMetricsHandler() {
@@ -219049,7 +219057,7 @@ function instrument() {
219049
219057
  }
219050
219058
  }
219051
219059
  instrument();
219052
- var meter = import_api.metrics.getMeter("publisher");
219060
+ var meter = publisherMeter();
219053
219061
  var httpRequestDuration = meter.createHistogram("http_server_request_duration_ms", {
219054
219062
  description: "Duration of HTTP requests in milliseconds",
219055
219063
  unit: "ms",
@@ -224473,14 +224481,13 @@ var getProcessedPublisherConfig = (serverRoot) => {
224473
224481
  init_errors();
224474
224482
 
224475
224483
  // src/query_cap_metrics.ts
224476
- var import_api2 = __toESM(require_src(), 1);
224477
224484
  var capExceededCounter = null;
224478
224485
  var configGaugesInstalled = false;
224479
224486
  function ensureCapTelemetry() {
224480
224487
  if (capExceededCounter && configGaugesInstalled) {
224481
224488
  return capExceededCounter;
224482
224489
  }
224483
- const meter2 = import_api2.metrics.getMeter("publisher");
224490
+ const meter2 = publisherMeter();
224484
224491
  if (!capExceededCounter) {
224485
224492
  capExceededCounter = meter2.createCounter("publisher_query_cap_exceeded_total", {
224486
224493
  description: "Queries rejected with 413 because the row or byte cap was exceeded. Labels: cap_type ('rows'|'bytes'), source ('connection_sql'|'model_query'|'notebook_cell')."
@@ -224518,7 +224525,6 @@ function recordQueryCapExceeded(capType, source) {
224518
224525
  init_logger();
224519
224526
 
224520
224527
  // src/query_timeout.ts
224521
- var import_api3 = __toESM(require_src(), 1);
224522
224528
  init_errors();
224523
224529
  var queryTimeoutCounter = null;
224524
224530
  var timeoutTelemetryInitialized = false;
@@ -224526,7 +224532,7 @@ function ensureTimeoutTelemetry() {
224526
224532
  if (queryTimeoutCounter && timeoutTelemetryInitialized) {
224527
224533
  return queryTimeoutCounter;
224528
224534
  }
224529
- const meter2 = import_api3.metrics.getMeter("publisher");
224535
+ const meter2 = publisherMeter();
224530
224536
  if (!queryTimeoutCounter) {
224531
224537
  queryTimeoutCounter = meter2.createCounter("publisher_query_timeout_total", {
224532
224538
  description: "Queries aborted because PUBLISHER_QUERY_TIMEOUT_MS elapsed before the underlying SDK call completed"
@@ -230137,7 +230143,10 @@ async function listTablesForSchema(connection, schemaName, malloyConnection, tab
230137
230143
  async function listTablesForBigQuery(connection, schemaName, malloyConnection, tableNames) {
230138
230144
  try {
230139
230145
  const bigquery = createBigQueryClient(connection);
230140
- const dataset = bigquery.dataset(schemaName);
230146
+ const lastDot = schemaName.lastIndexOf(".");
230147
+ const dataset = lastDot === -1 ? bigquery.dataset(schemaName) : bigquery.dataset(schemaName.slice(lastDot + 1), {
230148
+ projectId: schemaName.slice(0, lastDot)
230149
+ });
230141
230150
  const [tables] = await dataset.getTables();
230142
230151
  let names = tables.map((table) => table.id).filter((id) => id !== undefined);
230143
230152
  if (tableNames) {
@@ -237687,7 +237696,6 @@ class StorageManager {
237687
237696
  }
237688
237697
 
237689
237698
  // src/service/environment.ts
237690
- var import_api7 = __toESM(require_src(), 1);
237691
237699
  import { MalloyError as MalloyError4, Runtime as Runtime2 } from "@malloydata/malloy";
237692
237700
  init_constants();
237693
237701
  init_errors();
@@ -237698,17 +237706,19 @@ import * as path8 from "path";
237698
237706
  import { pathToFileURL as pathToFileURL2 } from "url";
237699
237707
 
237700
237708
  // src/materialization_metrics.ts
237701
- var import_api4 = __toESM(require_src(), 1);
237702
237709
  var resetHooks = [];
237703
237710
  function lazyCounter(name, description) {
237704
237711
  let instrument2 = null;
237705
237712
  resetHooks.push(() => instrument2 = null);
237706
- return () => instrument2 ??= import_api4.metrics.getMeter("publisher").createCounter(name, { description });
237713
+ return () => instrument2 ??= publisherMeter().createCounter(name, { description });
237707
237714
  }
237708
237715
  function lazyHistogram(name, description, unit) {
237709
237716
  let instrument2 = null;
237710
237717
  resetHooks.push(() => instrument2 = null);
237711
- return () => instrument2 ??= import_api4.metrics.getMeter("publisher").createHistogram(name, { description, unit });
237718
+ return () => instrument2 ??= publisherMeter().createHistogram(name, {
237719
+ description,
237720
+ unit
237721
+ });
237712
237722
  }
237713
237723
  var runCounter = lazyCounter("publisher_materialization_runs_total", "Materialization builds completed. Labels: mode ('auto'|'orchestrated'), outcome ('success'|'failed'|'cancelled').");
237714
237724
  var runDuration = lazyHistogram("publisher_materialization_run_duration_ms", "Wall-clock duration of a materialization build. Label: mode ('auto'|'orchestrated').", "ms");
@@ -237828,12 +237838,6 @@ async function fetchManifestEntries(uri) {
237828
237838
  }
237829
237839
 
237830
237840
  // src/service/package.ts
237831
- init_package_load_pool();
237832
- init_constants();
237833
- init_errors();
237834
- init_logger();
237835
- var import_api6 = __toESM(require_src(), 1);
237836
- var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
237837
237841
  import * as fs6 from "fs/promises";
237838
237842
  import * as path7 from "path";
237839
237843
  import"@malloydata/db-duckdb/native";
@@ -237845,13 +237849,17 @@ import {
237845
237849
  MalloyConfig as MalloyConfig3,
237846
237850
  MalloyError as MalloyError3
237847
237851
  } from "@malloydata/malloy";
237852
+ init_package_load_pool();
237853
+ init_constants();
237854
+ init_errors();
237855
+ init_logger();
237856
+ var import_recursive_readdir = __toESM(require_recursive_readdir(), 1);
237848
237857
 
237849
237858
  // src/service/build_plan.ts
237850
237859
  init_constants();
237851
237860
  init_logger();
237852
237861
 
237853
237862
  // src/service/model.ts
237854
- var import_api5 = __toESM(require_src(), 1);
237855
237863
  import {
237856
237864
  Annotations as Annotations2,
237857
237865
  API,
@@ -238288,6 +238296,24 @@ function assertWithinModelResponseLimits(rowCount, serializedBytes, { maxRows, m
238288
238296
  }
238289
238297
  }
238290
238298
 
238299
+ // src/service/query_text.ts
238300
+ function extractRunTargetSourceName(query) {
238301
+ if (!query)
238302
+ return;
238303
+ const runMatch = query.match(/run\s*:\s*(?:`([^`]+)`|(\w+))\s*->/);
238304
+ const arrowMatch = query.match(/^\s*(?:`([^`]+)`|(\w+))\s*->/m);
238305
+ return runMatch?.[1] ?? runMatch?.[2] ?? arrowMatch?.[1] ?? arrowMatch?.[2];
238306
+ }
238307
+ function buildSourceAliasMap(query) {
238308
+ const aliasOf = new Map;
238309
+ const declRe = /source\s*:\s*(?:`([^`]+)`|(\w+))\s+is\s+(?:`([^`]+)`|(\w+))/g;
238310
+ let match;
238311
+ while ((match = declRe.exec(query)) !== null) {
238312
+ aliasOf.set(match[1] ?? match[2], match[3] ?? match[4]);
238313
+ }
238314
+ return aliasOf;
238315
+ }
238316
+
238291
238317
  // src/service/source_extraction.ts
238292
238318
  import {
238293
238319
  isSourceDef
@@ -238390,7 +238416,7 @@ class Model {
238390
238416
  fileLevelAuthorize = [];
238391
238417
  discoveryCurationEnabled = false;
238392
238418
  queryBoundary = { mode: "all", exploresDeclared: false, isQueryEntryPoint: true };
238393
- meter = import_api5.metrics.getMeter("publisher");
238419
+ meter = publisherMeter();
238394
238420
  queryExecutionHistogram = this.meter.createHistogram("malloy_model_query_duration", {
238395
238421
  description: "How long it takes to execute a Malloy model query",
238396
238422
  unit: "ms"
@@ -238463,7 +238489,7 @@ class Model {
238463
238489
  deny();
238464
238490
  }
238465
238491
  async assertAuthorizedForText(text, givens) {
238466
- await this.assertAuthorized(this.extractSourceName(text), givens);
238492
+ await this.assertAuthorized(extractRunTargetSourceName(text), givens);
238467
238493
  }
238468
238494
  async assertAuthorizedForRunnable(runnable, givens) {
238469
238495
  await this.assertAuthorized(await this.resolveAuthorizeSourceFromRunnable(runnable), givens);
@@ -238481,18 +238507,11 @@ class Model {
238481
238507
  } catch {}
238482
238508
  return;
238483
238509
  }
238484
- extractSourceName(query) {
238485
- if (!query)
238486
- return;
238487
- const runMatch = query.match(/run\s*:\s*(?:`([^`]+)`|(\w+))\s*->/);
238488
- const arrowMatch = query.match(/^\s*(?:`([^`]+)`|(\w+))\s*->/m);
238489
- return runMatch?.[1] ?? runMatch?.[2] ?? arrowMatch?.[1] ?? arrowMatch?.[2];
238490
- }
238491
238510
  resolveFilterSource(query) {
238492
- const target = this.extractSourceName(query);
238511
+ const target = extractRunTargetSourceName(query);
238493
238512
  if (!target || !query)
238494
238513
  return;
238495
- const aliasOf = Model.buildAliasMap(query);
238514
+ const aliasOf = buildSourceAliasMap(query);
238496
238515
  let current = target;
238497
238516
  const seen = new Set;
238498
238517
  while (current && !seen.has(current)) {
@@ -238655,7 +238674,7 @@ class Model {
238655
238674
  throw new NotQueryableError(`No queryable source "${sourceName}".`);
238656
238675
  }
238657
238676
  if (query) {
238658
- const target = this.extractSourceName(query);
238677
+ const target = extractRunTargetSourceName(query);
238659
238678
  if (target && !curatedSources.has(target) && !this.derivesFromCurated(target, query) && this.sources?.some((s) => s.name === target)) {
238660
238679
  throw new NotQueryableError(`No queryable source "${target}".`);
238661
238680
  }
@@ -238690,7 +238709,7 @@ class Model {
238690
238709
  }
238691
238710
  derivesFromCurated(name, query) {
238692
238711
  const curated = this.curatedSourceNames();
238693
- const aliasOf = Model.buildAliasMap(query);
238712
+ const aliasOf = buildSourceAliasMap(query);
238694
238713
  let current = name;
238695
238714
  const seen = new Set;
238696
238715
  while (current && !seen.has(current)) {
@@ -238701,15 +238720,6 @@ class Model {
238701
238720
  }
238702
238721
  return false;
238703
238722
  }
238704
- static buildAliasMap(query) {
238705
- const aliasOf = new Map;
238706
- const declRe = /source\s*:\s*(?:`([^`]+)`|(\w+))\s+is\s+(?:`([^`]+)`|(\w+))/g;
238707
- let match;
238708
- while ((match = declRe.exec(query)) !== null) {
238709
- aliasOf.set(match[1] ?? match[2], match[3] ?? match[4]);
238710
- }
238711
- return aliasOf;
238712
- }
238713
238723
  async validateRenderTags() {
238714
238724
  const mm = this.modelMaterializer;
238715
238725
  if (!mm) {
@@ -238786,7 +238796,7 @@ class Model {
238786
238796
  if (!this.modelMaterializer || !this.modelDef || !this.modelInfo)
238787
238797
  throw new BadRequestError("Model has no queryable entities.");
238788
238798
  const boundary = this.assertQueryBoundaryEarly(sourceName, queryName, query);
238789
- const earlySource = sourceName || (queryName ? this.queries?.find((q) => q.name === queryName)?.sourceName : undefined) || this.extractSourceName(query);
238799
+ const earlySource = sourceName || (queryName ? this.queries?.find((q) => q.name === queryName)?.sourceName : undefined) || extractRunTargetSourceName(query);
238790
238800
  if (earlySource) {
238791
238801
  await this.assertAuthorized(earlySource, givens ?? {});
238792
238802
  }
@@ -238971,7 +238981,7 @@ run: ${sourceName ? sourceName + "->" : ""}${queryName}`;
238971
238981
  try {
238972
238982
  let runnableToExecute = cell.runnable;
238973
238983
  if (!bypassFilters && cell.modelMaterializer) {
238974
- const effectiveSource = this.extractSourceName(cell.text);
238984
+ const effectiveSource = extractRunTargetSourceName(cell.text);
238975
238985
  if (effectiveSource) {
238976
238986
  const filters = this.getFilters(effectiveSource);
238977
238987
  if (filters.length > 0) {
@@ -239441,7 +239451,7 @@ class Package {
239441
239451
  manifestEntryCount = 0;
239442
239452
  boundManifestUri = null;
239443
239453
  buildPlan = null;
239444
- static meter = import_api6.metrics.getMeter("publisher");
239454
+ static meter = publisherMeter();
239445
239455
  static packageLoadHistogram = this.meter.createHistogram("malloy_package_load_duration", {
239446
239456
  description: "Time taken to load a Malloy package",
239447
239457
  unit: "ms"
@@ -239555,7 +239565,8 @@ class Package {
239555
239565
  resource: `${API_PREFIX}/environments/${environmentName}/packages/${packageName}`,
239556
239566
  explores: outcome.packageMetadata.explores,
239557
239567
  queryableSources: outcome.packageMetadata.queryableSources,
239558
- manifestLocation: outcome.packageMetadata.manifestLocation ?? null
239568
+ manifestLocation: outcome.packageMetadata.manifestLocation ?? null,
239569
+ materialization: outcome.packageMetadata.materialization ?? null
239559
239570
  };
239560
239571
  const models = new Map;
239561
239572
  for (const sm of outcome.models) {
@@ -239919,7 +239930,7 @@ var packageAdmissionRejectionsCounter = null;
239919
239930
  function getQueryAdmissionRejectionsCounter() {
239920
239931
  if (queryAdmissionRejectionsCounter)
239921
239932
  return queryAdmissionRejectionsCounter;
239922
- queryAdmissionRejectionsCounter = import_api7.metrics.getMeter("publisher").createCounter("publisher_query_admission_rejections_total", {
239933
+ queryAdmissionRejectionsCounter = publisherMeter().createCounter("publisher_query_admission_rejections_total", {
239923
239934
  description: "Queries rejected with 503 because Environment.assertCanAdmitQuery() observed memory back-pressure"
239924
239935
  });
239925
239936
  return queryAdmissionRejectionsCounter;
@@ -239928,7 +239939,7 @@ function getPackageAdmissionRejectionsCounter() {
239928
239939
  if (packageAdmissionRejectionsCounter) {
239929
239940
  return packageAdmissionRejectionsCounter;
239930
239941
  }
239931
- packageAdmissionRejectionsCounter = import_api7.metrics.getMeter("publisher").createCounter("publisher_package_admission_rejections_total", {
239942
+ packageAdmissionRejectionsCounter = publisherMeter().createCounter("publisher_package_admission_rejections_total", {
239932
239943
  description: "Package loads rejected with 503 because Environment.assertCanAdmitNewPackage() observed memory back-pressure"
239933
239944
  });
239934
239945
  return packageAdmissionRejectionsCounter;
@@ -241853,14 +241864,13 @@ var setFilterDeprecationHeaders = (res, options) => {
241853
241864
 
241854
241865
  // src/heap_check.ts
241855
241866
  init_logger();
241856
- var import_api8 = __toESM(require_src(), 1);
241857
241867
  import * as v8 from "v8";
241858
241868
  var heapGaugesInstalled = false;
241859
241869
  function installHeapGauges() {
241860
241870
  if (heapGaugesInstalled)
241861
241871
  return;
241862
241872
  heapGaugesInstalled = true;
241863
- const meter2 = import_api8.metrics.getMeter("publisher");
241873
+ const meter2 = publisherMeter();
241864
241874
  meter2.createObservableGauge("publisher_heap_size_limit_bytes", {
241865
241875
  description: "V8 heap_size_limit (--max-old-space-size). Compare with PUBLISHER_MAX_MEMORY_BYTES.",
241866
241876
  unit: "By"
@@ -241895,7 +241905,6 @@ function checkHeapConfiguration(options = {}) {
241895
241905
  }
241896
241906
 
241897
241907
  // src/query_concurrency.ts
241898
- var import_api9 = __toESM(require_src(), 1);
241899
241908
  init_errors();
241900
241909
  init_logger();
241901
241910
  var active = 0;
@@ -241905,7 +241914,7 @@ function ensureConcurrencyTelemetry() {
241905
241914
  if (queryConcurrencyRejectionsCounter && concurrencyTelemetryInitialized) {
241906
241915
  return queryConcurrencyRejectionsCounter;
241907
241916
  }
241908
- const meter2 = import_api9.metrics.getMeter("publisher");
241917
+ const meter2 = publisherMeter();
241909
241918
  queryConcurrencyRejectionsCounter = meter2.createCounter("publisher_query_concurrency_rejections_total", {
241910
241919
  description: "Queries rejected with 503 because the per-pod PUBLISHER_MAX_CONCURRENT_QUERIES cap was reached"
241911
241920
  });
@@ -249054,7 +249063,6 @@ class MaterializationService {
249054
249063
 
249055
249064
  // src/service/package_memory_governor.ts
249056
249065
  init_logger();
249057
- var import_api10 = __toESM(require_src(), 1);
249058
249066
  var DEFAULT_RSS_SAMPLER = () => process.memoryUsage().rss;
249059
249067
 
249060
249068
  class PackageMemoryGovernor {
@@ -249072,7 +249080,7 @@ class PackageMemoryGovernor {
249072
249080
  this.rssSampler = rssSampler ?? DEFAULT_RSS_SAMPLER;
249073
249081
  this.highWaterBytes = Math.floor(config.maxMemoryBytes * config.highWaterFraction);
249074
249082
  this.lowWaterBytes = Math.floor(config.maxMemoryBytes * config.lowWaterFraction);
249075
- const meter2 = import_api10.metrics.getMeter("publisher");
249083
+ const meter2 = publisherMeter();
249076
249084
  meter2.createObservableGauge("publisher_process_rss_bytes", {
249077
249085
  description: "Current resident set size of the publisher process in bytes",
249078
249086
  unit: "By"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@malloy-publisher/server",
3
3
  "description": "Malloy Publisher Server",
4
- "version": "0.0.212",
4
+ "version": "0.0.214",
5
5
  "main": "dist/server.mjs",
6
6
  "bin": {
7
7
  "malloy-publisher": "dist/server.mjs"
@@ -195,9 +195,9 @@ export class PackageController {
195
195
 
196
196
  /**
197
197
  * Run the right downloader for the given location into `targetPath`.
198
- * This used to point at the canonical package directory, but the
199
- * install pipeline now passes a sibling staging dir so the long-running
200
- * download doesn't hold the per-package mutex.
198
+ * Callers pass a sibling staging dir (not the canonical package
199
+ * directory) so the long-running download doesn't hold the per-package
200
+ * mutex.
201
201
  */
202
202
  private async downloadInto(
203
203
  environmentName: string,
package/src/heap_check.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { metrics } from "@opentelemetry/api";
1
+ import { publisherMeter } from "./telemetry";
2
2
  import * as v8 from "v8";
3
3
 
4
4
  import { logger } from "./logger";
@@ -32,7 +32,7 @@ let heapGaugesInstalled = false;
32
32
  function installHeapGauges(): void {
33
33
  if (heapGaugesInstalled) return;
34
34
  heapGaugesInstalled = true;
35
- const meter = metrics.getMeter("publisher");
35
+ const meter = publisherMeter();
36
36
  meter
37
37
  .createObservableGauge("publisher_heap_size_limit_bytes", {
38
38
  description:
@@ -1,5 +1,4 @@
1
1
  import { monitorEventLoopDelay } from "node:perf_hooks";
2
- import { metrics } from "@opentelemetry/api";
3
2
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
4
3
  import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
5
4
  import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
@@ -18,6 +17,7 @@ import {
18
17
  } from "@opentelemetry/semantic-conventions";
19
18
  import type { NextFunction, Request, Response } from "express";
20
19
  import { logger } from "./logger";
20
+ import { publisherMeter } from "./telemetry";
21
21
 
22
22
  let prometheusExporter: PrometheusExporter | null = null;
23
23
  let sdk: NodeSDK | null = null;
@@ -98,7 +98,7 @@ instrument();
98
98
 
99
99
  // --- HTTP metrics middleware ---
100
100
 
101
- const meter = metrics.getMeter("publisher");
101
+ const meter = publisherMeter();
102
102
 
103
103
  const httpRequestDuration = meter.createHistogram(
104
104
  "http_server_request_duration_ms",
@@ -12,7 +12,8 @@
12
12
  * (https://github.com/open-telemetry/opentelemetry-js/issues/3505).
13
13
  */
14
14
 
15
- import { metrics, type Counter, type Histogram } from "@opentelemetry/api";
15
+ import { type Counter, type Histogram } from "@opentelemetry/api";
16
+ import { publisherMeter } from "./telemetry";
16
17
 
17
18
  export type MaterializationMode = "auto" | "orchestrated";
18
19
  export type MaterializationOutcome = "success" | "failed" | "cancelled";
@@ -30,9 +31,7 @@ function lazyCounter(name: string, description: string): () => Counter {
30
31
  let instrument: Counter | null = null;
31
32
  resetHooks.push(() => (instrument = null));
32
33
  return () =>
33
- (instrument ??= metrics
34
- .getMeter("publisher")
35
- .createCounter(name, { description }));
34
+ (instrument ??= publisherMeter().createCounter(name, { description }));
36
35
  }
37
36
 
38
37
  function lazyHistogram(
@@ -43,9 +42,10 @@ function lazyHistogram(
43
42
  let instrument: Histogram | null = null;
44
43
  resetHooks.push(() => (instrument = null));
45
44
  return () =>
46
- (instrument ??= metrics
47
- .getMeter("publisher")
48
- .createHistogram(name, { description, unit }));
45
+ (instrument ??= publisherMeter().createHistogram(name, {
46
+ description,
47
+ unit,
48
+ }));
49
49
  }
50
50
 
51
51
  const runCounter = lazyCounter(
@@ -54,8 +54,7 @@
54
54
  * - **Job timeout.** `PACKAGE_LOAD_JOB_TIMEOUT_MS` (default 120s) caps a
55
55
  * single package load. On timeout we **terminate the worker** and
56
56
  * reject the caller — we do not silently fall back, and we do not
57
- * leave a zombie compile burning CPU on the worker (that's the
58
- * exact scenario PR-#767's job-timeout-without-terminate had).
57
+ * leave a zombie compile burning CPU on the worker.
59
58
  *
60
59
  * Schema-fetch RPC routing
61
60
  * ------------------------
@@ -234,6 +233,7 @@ export interface LoadPackageOutcome {
234
233
  explores?: string[];
235
234
  queryableSources?: "declared" | "all";
236
235
  manifestLocation?: string | null;
236
+ materialization?: { schedule: string | null } | null;
237
237
  };
238
238
  models: Array<
239
239
  Omit<SerializedModel, "modelDef" | "sourceInfos"> & {
@@ -16,7 +16,7 @@
16
16
  * duplicates the in-memory DB state and adds native-module load
17
17
  * latency to every worker spawn. Database probing (`readDatabases`)
18
18
  * stays on the main thread where it can reuse the package's existing
19
- * DuckDB connection (see PR #772).
19
+ * DuckDB connection.
20
20
  *
21
21
  * Boundary
22
22
  * --------
@@ -83,6 +83,7 @@ import { HackyDataStylesAccumulator } from "../data_styles";
83
83
  import { ModelCompilationError } from "../errors";
84
84
  import { validateAuthorizeProbes } from "../service/authorize";
85
85
  import { type FilterDefinition } from "../service/filter";
86
+ import { parsePackageMaterialization } from "../service/package_manifest";
86
87
  import {
87
88
  extractQueriesFromModelDef,
88
89
  extractSourcesFromModelDef,
@@ -382,6 +383,7 @@ async function readPackageMetadata(packagePath: string): Promise<{
382
383
  explores?: string[];
383
384
  queryableSources?: "declared" | "all";
384
385
  manifestLocation?: string | null;
386
+ materialization?: { schedule: string | null } | null;
385
387
  }> {
386
388
  const manifestPath = path.join(packagePath, PACKAGE_MANIFEST_NAME);
387
389
  const contents = await fs.promises.readFile(manifestPath, "utf8");
@@ -391,6 +393,7 @@ async function readPackageMetadata(packagePath: string): Promise<{
391
393
  explores?: string[];
392
394
  queryableSources?: unknown;
393
395
  manifestLocation?: unknown;
396
+ materialization?: unknown;
394
397
  };
395
398
  return {
396
399
  name: parsed.name,
@@ -407,6 +410,9 @@ async function readPackageMetadata(packagePath: string): Promise<{
407
410
  typeof parsed.manifestLocation === "string"
408
411
  ? parsed.manifestLocation
409
412
  : null,
413
+ // Package-level Malloy Persistence policy; surfaced to the control plane,
414
+ // which owns scheduling. Only `schedule` is read today.
415
+ materialization: parsePackageMaterialization(parsed.materialization),
410
416
  };
411
417
  }
412
418
 
@@ -17,7 +17,7 @@
17
17
  *
18
18
  * Embedded database probing (`.parquet` / `.csv` schema + row count)
19
19
  * stays on the main thread — it reuses the package's existing DuckDB
20
- * connection (PR #772) and the probe queries are async-IO-bound, not
20
+ * connection and the probe queries are async-IO-bound, not
21
21
  * CPU-bound. Keeping all native-DB handles on the main thread also
22
22
  * sidesteps Bun crash 0x20131 where duckdb-native cannot be loaded
23
23
  * into more than one isolate of the same process.
@@ -27,8 +27,8 @@
27
27
  * (live native handles can't cross the worker boundary).
28
28
  * - Lazy-hydrating each model's `ModelMaterializer` from `modelDef`
29
29
  * via `Runtime._loadModelFromModelDef` on first query — NO
30
- * recompile. This is what closes the loop on PR #767's original
31
- * "first-query recompile on main thread" gap.
30
+ * recompile, so the first query never re-runs the compiler on
31
+ * the main thread.
32
32
  *
33
33
  * Per-model compile failures are returned in-band on
34
34
  * `SerializedModel.compilationError` so a single bad model doesn't
@@ -179,6 +179,7 @@ export interface LoadPackageResult {
179
179
  explores?: string[];
180
180
  queryableSources?: "declared" | "all";
181
181
  manifestLocation?: string | null;
182
+ materialization?: { schedule: string | null } | null;
182
183
  };
183
184
  models: SerializedModel[];
184
185
  /** Wall-clock ms inside the worker for the full package load. */
@@ -25,7 +25,8 @@
25
25
  * is one boolean check after the first 413.
26
26
  */
27
27
 
28
- import { metrics, type Counter } from "@opentelemetry/api";
28
+ import { type Counter } from "@opentelemetry/api";
29
+ import { publisherMeter } from "./telemetry";
29
30
 
30
31
  import { getMaxQueryRows, getMaxResponseBytes } from "./config";
31
32
 
@@ -39,7 +40,7 @@ function ensureCapTelemetry(): Counter {
39
40
  if (capExceededCounter && configGaugesInstalled) {
40
41
  return capExceededCounter;
41
42
  }
42
- const meter = metrics.getMeter("publisher");
43
+ const meter = publisherMeter();
43
44
  if (!capExceededCounter) {
44
45
  capExceededCounter = meter.createCounter(
45
46
  "publisher_query_cap_exceeded_total",
@@ -1,4 +1,5 @@
1
- import { metrics, type Counter } from "@opentelemetry/api";
1
+ import { type Counter } from "@opentelemetry/api";
2
+ import { publisherMeter } from "./telemetry";
2
3
  import type { NextFunction, Request, RequestHandler, Response } from "express";
3
4
 
4
5
  import { getMaxConcurrentQueries } from "./config";
@@ -30,7 +31,7 @@ function ensureConcurrencyTelemetry(): Counter {
30
31
  if (queryConcurrencyRejectionsCounter && concurrencyTelemetryInitialized) {
31
32
  return queryConcurrencyRejectionsCounter;
32
33
  }
33
- const meter = metrics.getMeter("publisher");
34
+ const meter = publisherMeter();
34
35
  queryConcurrencyRejectionsCounter = meter.createCounter(
35
36
  "publisher_query_concurrency_rejections_total",
36
37
  {