@malloy-publisher/server 0.0.204 → 0.0.206

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 (100) hide show
  1. package/build.ts +10 -1
  2. package/dist/app/api-doc.yaml +494 -397
  3. package/dist/app/assets/{EnvironmentPage-CX06cjOF.js → EnvironmentPage-BYwBeC2F.js} +1 -1
  4. package/dist/app/assets/HomePage-ivu4vdpj.js +1 -0
  5. package/dist/app/assets/{MainPage-nUJ9YatG.js → MainPage-B2DnHEDU.js} +2 -2
  6. package/dist/app/assets/MaterializationsPage-BZEuwF9P.js +1 -0
  7. package/dist/app/assets/ModelPage-Dpu3bfPg.js +1 -0
  8. package/dist/app/assets/{PackagePage-BaEVdEAG.js → PackagePage-B8PwcRHt.js} +1 -1
  9. package/dist/app/assets/{RouteError-BShQjZio.js → RouteError-BhbywAeC.js} +1 -1
  10. package/dist/app/assets/{WorkbookPage-CBn6ZjJW.js → WorkbookPage-C-JXsJG0.js} +1 -1
  11. package/dist/app/assets/{core-DECXYL4E.es-OaRfXwuQ.js → core-pPlPr7jK.es-CNEOlxKB.js} +1 -1
  12. package/dist/app/assets/{index-BLfPC1gy.js → index-BHEm8Egc.js} +1 -1
  13. package/dist/app/assets/{index-Dy3YhAZQ.js → index-BsvDrV14.js} +1 -1
  14. package/dist/app/assets/{index-DqiJ0bWp.js → index-ChR1fKR2.js} +134 -134
  15. package/dist/app/assets/{index.umd-DAN9K8yC.js → index.umd-BVLPYNuj.js} +1 -1
  16. package/dist/app/index.html +1 -1
  17. package/dist/instrumentation.mjs +18 -8
  18. package/dist/package_load_worker.mjs +19 -2
  19. package/dist/runtime/publisher.js +318 -0
  20. package/dist/server.mjs +1703 -1443
  21. package/package.json +5 -4
  22. package/scripts/bake-duckdb-extensions.js +104 -0
  23. package/src/constants.ts +12 -0
  24. package/src/controller/materialization.controller.ts +79 -35
  25. package/src/controller/package.controller.spec.ts +179 -0
  26. package/src/controller/package.controller.ts +60 -73
  27. package/src/controller/watch-mode.controller.ts +176 -46
  28. package/src/dto/package.dto.ts +16 -1
  29. package/src/errors.spec.ts +33 -0
  30. package/src/errors.ts +18 -0
  31. package/src/health.spec.ts +34 -1
  32. package/src/instrumentation.ts +33 -17
  33. package/src/materialization_metrics.ts +66 -0
  34. package/src/mcp/error_messages.spec.ts +35 -0
  35. package/src/mcp/error_messages.ts +14 -1
  36. package/src/mcp/handler_utils.ts +12 -0
  37. package/src/package_load/package_load_pool.ts +7 -1
  38. package/src/package_load/package_load_worker.ts +44 -4
  39. package/src/package_load/protocol.ts +7 -1
  40. package/src/runtime/publisher.js +318 -0
  41. package/src/server-old.ts +7 -149
  42. package/src/server.ts +488 -190
  43. package/src/service/authorize_integration.spec.ts +163 -2
  44. package/src/service/compile_authorize.spec.ts +85 -0
  45. package/src/service/environment.ts +270 -12
  46. package/src/service/environment_store.spec.ts +0 -81
  47. package/src/service/environment_store.ts +142 -34
  48. package/src/service/explore_visibility.spec.ts +434 -0
  49. package/src/service/exports_probe.spec.ts +107 -0
  50. package/src/service/manifest_loader.spec.ts +99 -0
  51. package/src/service/manifest_loader.ts +95 -0
  52. package/src/service/materialization_service.spec.ts +324 -512
  53. package/src/service/materialization_service.ts +816 -656
  54. package/src/service/model.ts +444 -13
  55. package/src/service/package.spec.ts +14 -2
  56. package/src/service/package.ts +271 -20
  57. package/src/service/package_rollback.spec.ts +190 -0
  58. package/src/service/package_worker_path.spec.ts +223 -0
  59. package/src/service/query_boundary.spec.ts +470 -0
  60. package/src/storage/DatabaseInterface.ts +35 -57
  61. package/src/storage/StorageManager.mock.ts +0 -9
  62. package/src/storage/StorageManager.ts +7 -290
  63. package/src/storage/duckdb/DuckDBConnection.ts +70 -124
  64. package/src/storage/duckdb/DuckDBRepository.ts +2 -35
  65. package/src/storage/duckdb/MaterializationRepository.ts +52 -27
  66. package/src/storage/duckdb/schema.ts +4 -20
  67. package/tests/fixtures/authorize-compile/model.malloy +9 -0
  68. package/tests/fixtures/authorize-compile/publisher.json +4 -0
  69. package/tests/fixtures/html-pages-nopublic/model.malloy +1 -0
  70. package/tests/fixtures/html-pages-nopublic/publisher.json +5 -0
  71. package/tests/fixtures/html-pages-test/data.csv +3 -0
  72. package/tests/fixtures/html-pages-test/public/assets/app.css +3 -0
  73. package/tests/fixtures/html-pages-test/public/data.json +1 -0
  74. package/tests/fixtures/html-pages-test/public/index.html +9 -0
  75. package/tests/fixtures/html-pages-test/public/sub/page2.html +9 -0
  76. package/tests/fixtures/html-pages-test/publisher.json +5 -0
  77. package/tests/fixtures/html-pages-test/report.malloy +1 -0
  78. package/tests/integration/authorize/compile_authorize_http.integration.spec.ts +92 -0
  79. package/tests/integration/duckdb_storage/duckdb_storage.integration.spec.ts +138 -0
  80. package/tests/integration/html_pages/html_pages.integration.spec.ts +378 -0
  81. package/tests/integration/materialization/manifest_binding.integration.spec.ts +175 -0
  82. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +277 -266
  83. package/tests/integration/watch-mode/watch_mode.integration.spec.ts +421 -0
  84. package/tests/unit/duckdb/attached_databases.test.ts +111 -0
  85. package/tests/unit/duckdb/duckdb_connection.test.ts +181 -0
  86. package/tests/unit/duckdb/legacy_schema_migration.test.ts +7 -4
  87. package/tests/unit/duckdb/repositories.test.ts +208 -0
  88. package/dist/app/assets/HomePage-CNFt_eUU.js +0 -1
  89. package/dist/app/assets/MaterializationsPage-B5goxVXW.js +0 -1
  90. package/dist/app/assets/ModelPage-Ba7Xh4lL.js +0 -1
  91. package/src/controller/manifest.controller.ts +0 -38
  92. package/src/service/manifest_service.spec.ts +0 -206
  93. package/src/service/manifest_service.ts +0 -117
  94. package/src/service/materialized_table_gc.spec.ts +0 -384
  95. package/src/service/materialized_table_gc.ts +0 -231
  96. package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
  97. package/src/storage/duckdb/ManifestRepository.ts +0 -120
  98. package/src/storage/duckdb/manifest_store.spec.ts +0 -133
  99. package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -155
  100. package/tests/unit/storage/StorageManager.test.ts +0 -166
@@ -280,6 +280,221 @@ source: gated is duckdb.sql("select 1 as id")`,
280
280
  }
281
281
  });
282
282
 
283
+ it("rejects a package whose view carries an invalid renderer tag", async () => {
284
+ writeManifest();
285
+ // `# big_value { sparkline=... }` is a child-only renderer config placed on
286
+ // the view itself, with no activating big_value. The renderer declines to
287
+ // match it; without compile-time validation it renders as "[object Object]"
288
+ // at query time. Render-tag validation runs on the main thread after the
289
+ // worker hydrates the model, so the load must reject with a 424
290
+ // ModelCompilationError naming the offending view.
291
+ fs.writeFileSync(
292
+ path.join(tempDir, "bad_render.malloy"),
293
+ `source: nums is duckdb.sql("select 1 as a, 2 as b") extend {
294
+ measure: total is a.sum()
295
+ # big_value { sparkline=trend }
296
+ view: card is {
297
+ aggregate: total
298
+ nest:
299
+ # line_chart { size=spark }
300
+ trend is { group_by: a; aggregate: total }
301
+ }
302
+ }`,
303
+ );
304
+
305
+ const { ModelCompilationError } = await import("../errors");
306
+ const { malloyConfig, duckdb } = await makeMalloyConfig();
307
+ try {
308
+ let caught: unknown;
309
+ try {
310
+ await Package.create("env", "pkg", tempDir, malloyConfig);
311
+ } catch (err) {
312
+ caught = err;
313
+ }
314
+ expect(caught).toBeInstanceOf(ModelCompilationError);
315
+ expect((caught as Error).message).toContain(
316
+ "Invalid renderer configuration",
317
+ );
318
+ expect((caught as Error).message).toContain("nums -> card");
319
+ } finally {
320
+ await duckdb.close();
321
+ }
322
+ });
323
+
324
+ it("loads a package whose view renderer tags are valid", async () => {
325
+ writeManifest();
326
+ fs.writeFileSync(
327
+ path.join(tempDir, "good_render.malloy"),
328
+ `source: nums is duckdb.sql("select 1 as a, 2 as b") extend {
329
+ measure: total is a.sum()
330
+ # bar_chart
331
+ view: chart is { group_by: a; aggregate: total }
332
+ }`,
333
+ );
334
+
335
+ const { malloyConfig, duckdb } = await makeMalloyConfig();
336
+ try {
337
+ const pkg = await Package.create("env", "pkg", tempDir, malloyConfig);
338
+ expect(pkg.getModelPaths()).toEqual(["good_render.malloy"]);
339
+ } finally {
340
+ await duckdb.close();
341
+ }
342
+ });
343
+
344
+ it("rejects a package whose backtick-quoted (hyphenated) source has a bad view render tag", async () => {
345
+ writeManifest();
346
+ // A source whose name needs Malloy backtick-quoting (here, a hyphen). The
347
+ // validation target must quote the identifier; otherwise `run: bad-source
348
+ // -> card` fails to lex, loadQuery throws, the catch swallows it, and the
349
+ // misconfigured view is silently skipped -- which would defeat the feature
350
+ // for any quote-needing name (the repo already has such a source,
351
+ // `gated-source`, in authorize_integration.spec.ts).
352
+ fs.writeFileSync(
353
+ path.join(tempDir, "hyphen_render.malloy"),
354
+ `source: \`bad-source\` is duckdb.sql("select 1 as a, 2 as b") extend {
355
+ measure: total is a.sum()
356
+ # big_value { sparkline=trend }
357
+ view: card is {
358
+ aggregate: total
359
+ nest:
360
+ # line_chart { size=spark }
361
+ trend is { group_by: a; aggregate: total }
362
+ }
363
+ }`,
364
+ );
365
+
366
+ const { ModelCompilationError } = await import("../errors");
367
+ const { malloyConfig, duckdb } = await makeMalloyConfig();
368
+ try {
369
+ let caught: unknown;
370
+ try {
371
+ await Package.create("env", "pkg", tempDir, malloyConfig);
372
+ } catch (err) {
373
+ caught = err;
374
+ }
375
+ expect(caught).toBeInstanceOf(ModelCompilationError);
376
+ expect((caught as Error).message).toContain(
377
+ "Invalid renderer configuration",
378
+ );
379
+ expect((caught as Error).message).toContain("bad-source -> card");
380
+ } finally {
381
+ await duckdb.close();
382
+ }
383
+ });
384
+
385
+ it("rejects a package whose source name contains an escaped backtick with a bad view render tag", async () => {
386
+ writeManifest();
387
+ // Source name with a literal backtick (written escaped in Malloy as
388
+ // `wei\`rd`). The validation target must re-escape it; a bare wrap would
389
+ // produce `run: `wei`rd` -> ...`, where the embedded backtick closes the
390
+ // quote early, fails to lex, and the bad view is silently skipped. Mirrors
391
+ // malloy's own identifierCode escaping.
392
+ fs.writeFileSync(
393
+ path.join(tempDir, "backtick_render.malloy"),
394
+ `source: \`wei\\\`rd\` is duckdb.sql("select 1 as a, 2 as b") extend {
395
+ measure: total is a.sum()
396
+ # big_value { sparkline=trend }
397
+ view: card is {
398
+ aggregate: total
399
+ nest:
400
+ # line_chart { size=spark }
401
+ trend is { group_by: a; aggregate: total }
402
+ }
403
+ }`,
404
+ );
405
+
406
+ const { ModelCompilationError } = await import("../errors");
407
+ const { malloyConfig, duckdb } = await makeMalloyConfig();
408
+ try {
409
+ let caught: unknown;
410
+ try {
411
+ await Package.create("env", "pkg", tempDir, malloyConfig);
412
+ } catch (err) {
413
+ caught = err;
414
+ }
415
+ expect(caught).toBeInstanceOf(ModelCompilationError);
416
+ expect((caught as Error).message).toContain(
417
+ "Invalid renderer configuration",
418
+ );
419
+ expect((caught as Error).message).toContain("card");
420
+ } finally {
421
+ await duckdb.close();
422
+ }
423
+ });
424
+
425
+ it("rejects a .malloynb notebook whose source view carries an invalid renderer tag", async () => {
426
+ writeManifest();
427
+ fs.writeFileSync(
428
+ path.join(tempDir, "bad_render.malloynb"),
429
+ `>>>malloy
430
+ source: nums is duckdb.sql("select 1 as a, 2 as b") extend {
431
+ measure: total is a.sum()
432
+ # big_value { sparkline=trend }
433
+ view: card is {
434
+ aggregate: total
435
+ nest:
436
+ # line_chart { size=spark }
437
+ trend is { group_by: a; aggregate: total }
438
+ }
439
+ }`,
440
+ );
441
+
442
+ const { ModelCompilationError } = await import("../errors");
443
+ const { malloyConfig, duckdb } = await makeMalloyConfig();
444
+ try {
445
+ await expect(
446
+ Package.create("env", "pkg", tempDir, malloyConfig),
447
+ ).rejects.toBeInstanceOf(ModelCompilationError);
448
+ } finally {
449
+ await duckdb.close();
450
+ }
451
+ });
452
+
453
+ it("re-validates renderer tags on reload, recording a model that develops a bad tag as a per-model error", async () => {
454
+ writeManifest();
455
+ // Start valid so Package.create succeeds.
456
+ fs.writeFileSync(
457
+ path.join(tempDir, "m.malloy"),
458
+ `source: nums is duckdb.sql("select 1 as a, 2 as b") extend {
459
+ measure: total is a.sum()
460
+ # bar_chart
461
+ view: chart is { group_by: a; aggregate: total }
462
+ }`,
463
+ );
464
+
465
+ const { malloyConfig, duckdb } = await makeMalloyConfig();
466
+ try {
467
+ const pkg = await Package.create("env", "pkg", tempDir, malloyConfig);
468
+ expect(pkg.getModel("m.malloy")).toBeDefined();
469
+
470
+ // The model now develops a misconfigured render tag. Reload must catch
471
+ // it (not just Package.create) and surface it as the model's error,
472
+ // without aborting the whole reload.
473
+ fs.writeFileSync(
474
+ path.join(tempDir, "m.malloy"),
475
+ `source: nums is duckdb.sql("select 1 as a, 2 as b") extend {
476
+ measure: total is a.sum()
477
+ # big_value { sparkline=trend }
478
+ view: card is {
479
+ aggregate: total
480
+ nest:
481
+ # line_chart { size=spark }
482
+ trend is { group_by: a; aggregate: total }
483
+ }
484
+ }`,
485
+ );
486
+ await pkg.reloadAllModels({});
487
+
488
+ const reloaded = pkg.getModel("m.malloy");
489
+ expect(reloaded).toBeDefined();
490
+ await expect(reloaded!.getModel()).rejects.toThrow(
491
+ "Invalid renderer configuration",
492
+ );
493
+ } finally {
494
+ await duckdb.close();
495
+ }
496
+ });
497
+
283
498
  // NB: kept last in this describe — swapping the singleton for a
284
499
  // pre-shutdown pool also tears down the shared `pool` (the swap
285
500
  // implementation shuts down the outgoing singleton). Subsequent
@@ -304,6 +519,14 @@ source: gated is duckdb.sql("select 1 as id")`,
304
519
  ).rejects.toBeInstanceOf(ServiceUnavailableError);
305
520
  } finally {
306
521
  await duckdb.close();
522
+ // Replace the singleton with a fresh, live pool immediately. The
523
+ // outer afterAll also resets to null, but restoring here ensures the
524
+ // shut-down pool never outlives this test -- otherwise, under serial
525
+ // execution, a later spec file that lazily reuses the singleton via
526
+ // getPackageLoadPool() could observe the dead pool before afterAll
527
+ // runs and fail with "PackageLoadPool is shutting down".
528
+ pool = new PackageLoadPool(1);
529
+ await __setPackageLoadPoolForTests(pool);
307
530
  }
308
531
  });
309
532
  });