@malloy-publisher/server 0.0.205 → 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 (69) hide show
  1. package/dist/app/api-doc.yaml +363 -395
  2. package/dist/app/assets/{EnvironmentPage-CAge6UHD.js → EnvironmentPage-BYwBeC2F.js} +1 -1
  3. package/dist/app/assets/{HomePage-DhTe8qpa.js → HomePage-ivu4vdpj.js} +1 -1
  4. package/dist/app/assets/{MainPage-CeTxxGex.js → MainPage-B2DnHEDU.js} +2 -2
  5. package/dist/app/assets/{MaterializationsPage-CpDHB70t.js → MaterializationsPage-BZEuwF9P.js} +1 -1
  6. package/dist/app/assets/{ModelPage-D9sSMb75.js → ModelPage-Dpu3bfPg.js} +1 -1
  7. package/dist/app/assets/{PackagePage-LRqQWrFY.js → PackagePage-B8PwcRHt.js} +1 -1
  8. package/dist/app/assets/{RouteError-xT6kuCNw.js → RouteError-BhbywAeC.js} +1 -1
  9. package/dist/app/assets/{WorkbookPage-DsIh9svZ.js → WorkbookPage-C-JXsJG0.js} +1 -1
  10. package/dist/app/assets/{core-C2sQrwVu.es-Bjem0hym.js → core-pPlPr7jK.es-CNEOlxKB.js} +1 -1
  11. package/dist/app/assets/{index-BdOZDcce.js → index-BHEm8Egc.js} +1 -1
  12. package/dist/app/assets/{index-DHHAcY5o.js → index-BsvDrV14.js} +1 -1
  13. package/dist/app/assets/{index-RX3QOTde.js → index-ChR1fKR2.js} +127 -127
  14. package/dist/app/assets/{index.umd-D2WH3D-f.js → index.umd-BVLPYNuj.js} +1 -1
  15. package/dist/app/index.html +1 -1
  16. package/dist/instrumentation.mjs +18 -8
  17. package/dist/package_load_worker.mjs +19 -2
  18. package/dist/server.mjs +1151 -1264
  19. package/package.json +1 -1
  20. package/src/constants.ts +12 -0
  21. package/src/controller/materialization.controller.ts +79 -35
  22. package/src/controller/package.controller.spec.ts +179 -0
  23. package/src/controller/package.controller.ts +60 -73
  24. package/src/dto/package.dto.ts +16 -1
  25. package/src/errors.spec.ts +12 -0
  26. package/src/errors.ts +18 -0
  27. package/src/health.spec.ts +34 -1
  28. package/src/instrumentation.ts +33 -17
  29. package/src/materialization_metrics.ts +66 -0
  30. package/src/package_load/package_load_pool.ts +7 -1
  31. package/src/package_load/package_load_worker.ts +44 -4
  32. package/src/package_load/protocol.ts +7 -1
  33. package/src/server-old.ts +7 -149
  34. package/src/server.ts +9 -188
  35. package/src/service/authorize_integration.spec.ts +67 -0
  36. package/src/service/environment.ts +212 -12
  37. package/src/service/environment_store.spec.ts +0 -81
  38. package/src/service/environment_store.ts +0 -23
  39. package/src/service/explore_visibility.spec.ts +434 -0
  40. package/src/service/exports_probe.spec.ts +107 -0
  41. package/src/service/manifest_loader.spec.ts +99 -0
  42. package/src/service/manifest_loader.ts +95 -0
  43. package/src/service/materialization_service.spec.ts +324 -512
  44. package/src/service/materialization_service.ts +816 -656
  45. package/src/service/model.ts +400 -13
  46. package/src/service/package.spec.ts +14 -2
  47. package/src/service/package.ts +254 -14
  48. package/src/service/package_rollback.spec.ts +190 -0
  49. package/src/service/package_worker_path.spec.ts +223 -0
  50. package/src/service/query_boundary.spec.ts +470 -0
  51. package/src/storage/DatabaseInterface.ts +35 -57
  52. package/src/storage/StorageManager.mock.ts +0 -9
  53. package/src/storage/StorageManager.ts +7 -290
  54. package/src/storage/duckdb/DuckDBRepository.ts +2 -35
  55. package/src/storage/duckdb/MaterializationRepository.ts +52 -27
  56. package/src/storage/duckdb/schema.ts +4 -20
  57. package/tests/integration/materialization/manifest_binding.integration.spec.ts +175 -0
  58. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +277 -266
  59. package/tests/unit/duckdb/legacy_schema_migration.test.ts +7 -4
  60. package/src/controller/manifest.controller.ts +0 -38
  61. package/src/service/manifest_service.spec.ts +0 -206
  62. package/src/service/manifest_service.ts +0 -117
  63. package/src/service/materialized_table_gc.spec.ts +0 -384
  64. package/src/service/materialized_table_gc.ts +0 -231
  65. package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
  66. package/src/storage/duckdb/ManifestRepository.ts +0 -120
  67. package/src/storage/duckdb/manifest_store.spec.ts +0 -133
  68. package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -155
  69. package/tests/unit/storage/StorageManager.test.ts +0 -166
@@ -150,11 +150,14 @@ describe("DuckDB legacy projects schema cleanup", () => {
150
150
  ["p1"],
151
151
  );
152
152
  expect(mats.length).toBe(0);
153
- const manifests = await db.all<{ id: string }>(
154
- "SELECT id FROM build_manifests WHERE environment_id = ?",
155
- ["p1"],
153
+
154
+ // The legacy `build_manifests` table is dropped and not recreated: the
155
+ // two-round refactor folds the manifest into a JSON column on the
156
+ // `materializations` row, so no standalone manifest table remains.
157
+ const manifestTable = await db.all<{ name: string }>(
158
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='build_manifests'",
156
159
  );
157
- expect(manifests.length).toBe(0);
160
+ expect(manifestTable.length).toBe(0);
158
161
 
159
162
  await db.close();
160
163
  });
@@ -1,38 +0,0 @@
1
- import { EnvironmentStore } from "../service/environment_store";
2
- import { ManifestService } from "../service/manifest_service";
3
- import { resolveEnvironmentId } from "../service/resolve_environment";
4
-
5
- export class ManifestController {
6
- constructor(
7
- private environmentStore: EnvironmentStore,
8
- private manifestService: ManifestService,
9
- ) {}
10
-
11
- async getManifest(environmentName: string, packageName: string) {
12
- const repository = this.environmentStore.storageManager.getRepository();
13
- const environmentId = await resolveEnvironmentId(
14
- repository,
15
- environmentName,
16
- );
17
- // Verify the package exists so we return 404 instead of an empty manifest.
18
- const environment = await this.environmentStore.getEnvironment(
19
- environmentName,
20
- false,
21
- );
22
- await environment.getPackage(packageName, false);
23
- return this.manifestService.getManifest(environmentId, packageName);
24
- }
25
-
26
- async reloadManifest(environmentName: string, packageName: string) {
27
- const repository = this.environmentStore.storageManager.getRepository();
28
- const environmentId = await resolveEnvironmentId(
29
- repository,
30
- environmentName,
31
- );
32
- return this.manifestService.reloadManifest(
33
- environmentId,
34
- packageName,
35
- environmentName,
36
- );
37
- }
38
- }
@@ -1,206 +0,0 @@
1
- import { beforeEach, describe, expect, it } from "bun:test";
2
- import * as sinon from "sinon";
3
- import {
4
- BuildManifest,
5
- ManifestEntry,
6
- ManifestStore,
7
- ResourceRepository,
8
- } from "../storage/DatabaseInterface";
9
- import { ManifestService } from "./manifest_service";
10
- import { EnvironmentStore } from "./environment_store";
11
-
12
- function makeEntry(overrides: Partial<ManifestEntry> = {}): ManifestEntry {
13
- return {
14
- id: "entry-1",
15
- environmentId: "proj-1",
16
- packageName: "pkg",
17
- buildId: "build-abc",
18
- tableName: "my_table",
19
- sourceName: "my_source",
20
- connectionName: "duckdb",
21
- createdAt: new Date("2025-01-01"),
22
- updatedAt: new Date("2025-01-01"),
23
- ...overrides,
24
- };
25
- }
26
-
27
- function createMocks() {
28
- const sandbox = sinon.createSandbox();
29
-
30
- const manifestStore: sinon.SinonStubbedInstance<ManifestStore> = {
31
- getManifest: sandbox.stub(),
32
- writeEntry: sandbox.stub(),
33
- deleteEntry: sandbox.stub(),
34
- listEntries: sandbox.stub(),
35
- };
36
-
37
- const repository = {
38
- listManifestEntries: sandbox.stub(),
39
- } as unknown as sinon.SinonStubbedInstance<ResourceRepository>;
40
-
41
- const reloadAllModels = sandbox.stub().resolves();
42
- const reloadAllModelsForPackage = sandbox.stub().resolves();
43
-
44
- const pkg = {
45
- reloadAllModels,
46
- };
47
-
48
- const environment = {
49
- getPackage: sandbox.stub().resolves(pkg),
50
- reloadAllModelsForPackage,
51
- };
52
-
53
- const environmentStore = {
54
- storageManager: {
55
- getManifestStore: (_environmentId?: string) => manifestStore,
56
- getRepository: () => repository,
57
- },
58
- getEnvironment: sandbox.stub().resolves(environment),
59
- } as unknown as EnvironmentStore;
60
-
61
- const service = new ManifestService(environmentStore);
62
-
63
- return {
64
- sandbox,
65
- manifestStore,
66
- repository,
67
- environmentStore,
68
- environment,
69
- pkg,
70
- reloadAllModels,
71
- reloadAllModelsForPackage,
72
- service,
73
- };
74
- }
75
-
76
- describe("ManifestService", () => {
77
- let ctx: ReturnType<typeof createMocks>;
78
-
79
- beforeEach(() => {
80
- ctx = createMocks();
81
- });
82
-
83
- describe("getManifest", () => {
84
- it("should delegate to the manifest store", async () => {
85
- const manifest: BuildManifest = {
86
- entries: { "build-abc": { tableName: "tbl" } },
87
- strict: false,
88
- };
89
- ctx.manifestStore.getManifest.resolves(manifest);
90
-
91
- const result = await ctx.service.getManifest("proj-1", "pkg");
92
-
93
- expect(result).toEqual(manifest);
94
- expect(ctx.manifestStore.getManifest.calledWith("proj-1", "pkg")).toBe(
95
- true,
96
- );
97
- });
98
- });
99
-
100
- describe("writeEntry", () => {
101
- it("should delegate to the manifest store", async () => {
102
- ctx.manifestStore.writeEntry.resolves();
103
-
104
- await ctx.service.writeEntry(
105
- "proj-1",
106
- "pkg",
107
- "build-abc",
108
- "tbl",
109
- "src",
110
- "duckdb",
111
- );
112
-
113
- expect(ctx.manifestStore.writeEntry.calledOnce).toBe(true);
114
- const args = ctx.manifestStore.writeEntry.firstCall.args;
115
- expect(args[0]).toBe("proj-1");
116
- expect(args[1]).toBe("pkg");
117
- expect(args[2]).toBe("build-abc");
118
- expect(args[3]).toBe("tbl");
119
- expect(args[4]).toBe("src");
120
- expect(args[5]).toBe("duckdb");
121
- });
122
- });
123
-
124
- describe("deleteEntry", () => {
125
- it("should delegate to the manifest store", async () => {
126
- ctx.manifestStore.deleteEntry.resolves();
127
-
128
- await ctx.service.deleteEntry("proj-1", "entry-1");
129
-
130
- expect(ctx.manifestStore.deleteEntry.calledOnce).toBe(true);
131
- expect(ctx.manifestStore.deleteEntry.firstCall.args[0]).toBe(
132
- "entry-1",
133
- );
134
- });
135
- });
136
-
137
- describe("reloadManifest", () => {
138
- it("should get manifest, reload it onto the package, and return it", async () => {
139
- const manifest: BuildManifest = {
140
- entries: { "build-abc": { tableName: "tbl" } },
141
- strict: false,
142
- };
143
- ctx.manifestStore.getManifest.resolves(manifest);
144
-
145
- const result = await ctx.service.reloadManifest(
146
- "proj-1",
147
- "pkg",
148
- "my-environment",
149
- );
150
-
151
- expect(result).toEqual(manifest);
152
- expect(
153
- (ctx.environmentStore.getEnvironment as sinon.SinonStub).calledWith(
154
- "my-environment",
155
- false,
156
- ),
157
- ).toBe(true);
158
- expect(ctx.environment.getPackage.calledWith("pkg", false)).toBe(true);
159
- expect(
160
- ctx.reloadAllModelsForPackage.calledWith("pkg", manifest.entries),
161
- ).toBe(true);
162
- });
163
-
164
- it("should return an empty manifest when no entries exist", async () => {
165
- const emptyManifest: BuildManifest = {
166
- entries: {},
167
- strict: false,
168
- };
169
- ctx.manifestStore.getManifest.resolves(emptyManifest);
170
-
171
- const result = await ctx.service.reloadManifest(
172
- "proj-1",
173
- "pkg",
174
- "my-environment",
175
- );
176
-
177
- expect(result.entries).toEqual({});
178
- expect(ctx.reloadAllModelsForPackage.calledWith("pkg", {})).toBe(true);
179
- });
180
- });
181
-
182
- describe("listEntries", () => {
183
- it("should return entries from the manifest store", async () => {
184
- const entries = [
185
- makeEntry(),
186
- makeEntry({ id: "entry-2", buildId: "build-def" }),
187
- ];
188
- ctx.manifestStore.listEntries.resolves(entries);
189
-
190
- const result = await ctx.service.listEntries("proj-1", "pkg");
191
-
192
- expect(result).toEqual(entries);
193
- expect(ctx.manifestStore.listEntries.calledWith("proj-1", "pkg")).toBe(
194
- true,
195
- );
196
- });
197
-
198
- it("should return empty array when no entries exist", async () => {
199
- ctx.manifestStore.listEntries.resolves([]);
200
-
201
- const result = await ctx.service.listEntries("proj-1", "pkg");
202
-
203
- expect(result).toEqual([]);
204
- });
205
- });
206
- });
@@ -1,117 +0,0 @@
1
- import { logger } from "../logger";
2
- import {
3
- BuildManifest,
4
- ManifestEntry,
5
- ManifestStore,
6
- } from "../storage/DatabaseInterface";
7
- import { EnvironmentStore } from "./environment_store";
8
-
9
- /**
10
- * Manages build manifests that map source names to materialized table names.
11
- *
12
- * A build manifest is produced during materialization: each entry records which
13
- * DuckDB table backs a given Malloy source.
14
- *
15
- * Two-phase lifecycle:
16
- * 1. **Persist** – `writeEntry` stores individual entries to the DB as they
17
- * are produced during a build.
18
- * 2. **Reload** – `reloadManifest` reads the manifest from the DB and
19
- * recompiles all models in the package so the Malloy Runtime resolves
20
- * persist references to the materialized tables.
21
- *
22
- * All manifest operations delegate to the active {@link ManifestStore}, which
23
- * is either the local DuckDB store (standalone) or DuckLake (orchestrated).
24
- */
25
- export class ManifestService {
26
- constructor(private environmentStore: EnvironmentStore) {}
27
-
28
- private manifestStoreFor(environmentId: string): ManifestStore {
29
- return this.environmentStore.storageManager.getManifestStore(
30
- environmentId,
31
- );
32
- }
33
-
34
- async getManifest(
35
- environmentId: string,
36
- packageName: string,
37
- ): Promise<BuildManifest> {
38
- return this.manifestStoreFor(environmentId).getManifest(
39
- environmentId,
40
- packageName,
41
- );
42
- }
43
-
44
- async writeEntry(
45
- environmentId: string,
46
- packageName: string,
47
- buildId: string,
48
- tableName: string,
49
- sourceName: string,
50
- connectionName: string,
51
- ): Promise<void> {
52
- await this.manifestStoreFor(environmentId).writeEntry(
53
- environmentId,
54
- packageName,
55
- buildId,
56
- tableName,
57
- sourceName,
58
- connectionName,
59
- );
60
- }
61
-
62
- async deleteEntry(environmentId: string, entryId: string): Promise<void> {
63
- await this.manifestStoreFor(environmentId).deleteEntry(entryId);
64
- }
65
-
66
- /**
67
- * Read the manifest from storage and reload it onto this worker's
68
- * package so subsequent queries resolve persist references to
69
- * materialized tables. This is what `POST .../manifest?action=reload` calls
70
- * (used by orchestrated workers that need to pick up manifest state
71
- * produced elsewhere). Despite the name, nothing is loaded *into*
72
- * storage — the worker pulls the manifest down and recompiles.
73
- */
74
- async reloadManifest(
75
- environmentId: string,
76
- packageName: string,
77
- environmentName: string,
78
- ): Promise<BuildManifest> {
79
- const manifest = await this.getManifest(environmentId, packageName);
80
-
81
- const environment = await this.environmentStore.getEnvironment(
82
- environmentName,
83
- false,
84
- );
85
- // Ensure the package is loaded, then reload its models under the
86
- // per-package mutex so the disk reads are serialized against
87
- // installPackage / deletePackage.
88
- await environment.getPackage(packageName, false);
89
- await environment.reloadAllModelsForPackage(
90
- packageName,
91
- manifest.entries,
92
- );
93
-
94
- logger.info("Reloaded manifest and recompiled models", {
95
- environmentId,
96
- packageName,
97
- entryCount: Object.keys(manifest.entries).length,
98
- });
99
-
100
- return manifest;
101
- }
102
-
103
- /**
104
- * Get the manifest entries from the active store for inspection.
105
- * Routes through ManifestStore so DuckLake mode reads the shared
106
- * catalog instead of the local DuckDB table.
107
- */
108
- async listEntries(
109
- environmentId: string,
110
- packageName: string,
111
- ): Promise<ManifestEntry[]> {
112
- return this.manifestStoreFor(environmentId).listEntries(
113
- environmentId,
114
- packageName,
115
- );
116
- }
117
- }