@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.
- package/dist/app/api-doc.yaml +363 -395
- package/dist/app/assets/{EnvironmentPage-CAge6UHD.js → EnvironmentPage-BYwBeC2F.js} +1 -1
- package/dist/app/assets/{HomePage-DhTe8qpa.js → HomePage-ivu4vdpj.js} +1 -1
- package/dist/app/assets/{MainPage-CeTxxGex.js → MainPage-B2DnHEDU.js} +2 -2
- package/dist/app/assets/{MaterializationsPage-CpDHB70t.js → MaterializationsPage-BZEuwF9P.js} +1 -1
- package/dist/app/assets/{ModelPage-D9sSMb75.js → ModelPage-Dpu3bfPg.js} +1 -1
- package/dist/app/assets/{PackagePage-LRqQWrFY.js → PackagePage-B8PwcRHt.js} +1 -1
- package/dist/app/assets/{RouteError-xT6kuCNw.js → RouteError-BhbywAeC.js} +1 -1
- package/dist/app/assets/{WorkbookPage-DsIh9svZ.js → WorkbookPage-C-JXsJG0.js} +1 -1
- package/dist/app/assets/{core-C2sQrwVu.es-Bjem0hym.js → core-pPlPr7jK.es-CNEOlxKB.js} +1 -1
- package/dist/app/assets/{index-BdOZDcce.js → index-BHEm8Egc.js} +1 -1
- package/dist/app/assets/{index-DHHAcY5o.js → index-BsvDrV14.js} +1 -1
- package/dist/app/assets/{index-RX3QOTde.js → index-ChR1fKR2.js} +127 -127
- package/dist/app/assets/{index.umd-D2WH3D-f.js → index.umd-BVLPYNuj.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/instrumentation.mjs +18 -8
- package/dist/package_load_worker.mjs +19 -2
- package/dist/server.mjs +1151 -1264
- package/package.json +1 -1
- package/src/constants.ts +12 -0
- package/src/controller/materialization.controller.ts +79 -35
- package/src/controller/package.controller.spec.ts +179 -0
- package/src/controller/package.controller.ts +60 -73
- package/src/dto/package.dto.ts +16 -1
- package/src/errors.spec.ts +12 -0
- package/src/errors.ts +18 -0
- package/src/health.spec.ts +34 -1
- package/src/instrumentation.ts +33 -17
- package/src/materialization_metrics.ts +66 -0
- package/src/package_load/package_load_pool.ts +7 -1
- package/src/package_load/package_load_worker.ts +44 -4
- package/src/package_load/protocol.ts +7 -1
- package/src/server-old.ts +7 -149
- package/src/server.ts +9 -188
- package/src/service/authorize_integration.spec.ts +67 -0
- package/src/service/environment.ts +212 -12
- package/src/service/environment_store.spec.ts +0 -81
- package/src/service/environment_store.ts +0 -23
- package/src/service/explore_visibility.spec.ts +434 -0
- package/src/service/exports_probe.spec.ts +107 -0
- package/src/service/manifest_loader.spec.ts +99 -0
- package/src/service/manifest_loader.ts +95 -0
- package/src/service/materialization_service.spec.ts +324 -512
- package/src/service/materialization_service.ts +816 -656
- package/src/service/model.ts +400 -13
- package/src/service/package.spec.ts +14 -2
- package/src/service/package.ts +254 -14
- package/src/service/package_rollback.spec.ts +190 -0
- package/src/service/package_worker_path.spec.ts +223 -0
- package/src/service/query_boundary.spec.ts +470 -0
- package/src/storage/DatabaseInterface.ts +35 -57
- package/src/storage/StorageManager.mock.ts +0 -9
- package/src/storage/StorageManager.ts +7 -290
- package/src/storage/duckdb/DuckDBRepository.ts +2 -35
- package/src/storage/duckdb/MaterializationRepository.ts +52 -27
- package/src/storage/duckdb/schema.ts +4 -20
- package/tests/integration/materialization/manifest_binding.integration.spec.ts +175 -0
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +277 -266
- package/tests/unit/duckdb/legacy_schema_migration.test.ts +7 -4
- package/src/controller/manifest.controller.ts +0 -38
- package/src/service/manifest_service.spec.ts +0 -206
- package/src/service/manifest_service.ts +0 -117
- package/src/service/materialized_table_gc.spec.ts +0 -384
- package/src/service/materialized_table_gc.ts +0 -231
- package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
- package/src/storage/duckdb/ManifestRepository.ts +0 -120
- package/src/storage/duckdb/manifest_store.spec.ts +0 -133
- package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -155
- 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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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(
|
|
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
|
-
}
|