@malloy-publisher/server 0.0.223 → 0.0.225
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 +75 -0
- package/dist/app/assets/{EnvironmentPage-3wCdljov.js → EnvironmentPage-DYTeXDll.js} +1 -1
- package/dist/app/assets/{HomePage-K0GHwqq2.js → HomePage-pDK2BPJY.js} +1 -1
- package/dist/app/assets/{LightMode-CwU3kN4I.js → LightMode-C2bwGPY1.js} +1 -1
- package/dist/app/assets/{MainPage-CTncHE5T.js → MainPage-WtBulMH_.js} +2 -2
- package/dist/app/assets/{MaterializationsPage-DziAOD6-.js → MaterializationsPage-hMgOtflG.js} +1 -1
- package/dist/app/assets/{ModelPage-XrS2jXEc.js → ModelPage-B2N5kYII.js} +1 -1
- package/dist/app/assets/{PackagePage-BkwgFxG8.js → PackagePage-CEN90nQG.js} +1 -1
- package/dist/app/assets/{RouteError-9cIQga6p.js → RouteError-BG2c5Zf0.js} +1 -1
- package/dist/app/assets/{ThemeEditorPage-DWiCRfEe.js → ThemeEditorPage-DNfeUwEZ.js} +1 -1
- package/dist/app/assets/{WorkbookPage-Ce7FM_Po.js → WorkbookPage-NKI1BhFS.js} +1 -1
- package/dist/app/assets/{core-D9Hl0IDY.es-CrO01m4X.js → core-C6anj5c0.es-DDLHqpzt.js} +1 -1
- package/dist/app/assets/{index-D2sm5RBA.js → index-C6gZ6sSY.js} +5 -5
- package/dist/app/assets/{index-CtQm7kvp.js → index-CzNqKMVl.js} +1 -1
- package/dist/app/assets/{index-wJU_kzl2.js → index-DMQtnaf4.js} +2 -2
- package/dist/app/assets/{index-Dz8hgkmy.js → index-JXgvyZna.js} +1 -1
- package/dist/app/assets/{index-8E2uLeV9.js → index-OEjKNSYb.js} +2 -2
- package/dist/app/index.html +1 -1
- package/dist/server.mjs +411 -26
- package/dist/sshcrypto-8m50vnmb.node +0 -0
- package/package.json +12 -12
- package/src/controller/materialization.controller.spec.ts +72 -0
- package/src/controller/materialization.controller.ts +75 -11
- package/src/mcp/skills/skills_bundle.json +1 -1
- package/src/service/build_plan.spec.ts +119 -0
- package/src/service/build_plan.ts +143 -0
- package/src/service/environment.ts +3 -3
- package/src/service/freshness.spec.ts +183 -0
- package/src/service/freshness.ts +112 -0
- package/src/service/manifest_loader.spec.ts +33 -0
- package/src/service/manifest_loader.ts +17 -8
- package/src/service/materialization_cron_gate.spec.ts +61 -17
- package/src/service/materialization_service.spec.ts +42 -0
- package/src/service/materialization_service.ts +46 -3
- package/src/service/materialization_test_fixtures.ts +47 -14
- package/src/service/model.ts +54 -4
- package/src/service/package.ts +173 -35
- package/src/storage/DatabaseInterface.ts +24 -0
- package/tests/fixtures/persist-multi-level/data/orders.csv +5 -0
- package/tests/fixtures/persist-multi-level/multi_level.malloy +18 -0
- package/tests/fixtures/persist-multi-level/publisher.json +5 -0
- package/tests/integration/materialization/freshness_gate.integration.spec.ts +292 -0
- package/tests/integration/materialization/reference_manifest.integration.spec.ts +251 -0
|
Binary file
|
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.
|
|
4
|
+
"version": "0.0.225",
|
|
5
5
|
"main": "dist/server.mjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"malloy-publisher": "dist/server.mjs"
|
|
@@ -36,17 +36,17 @@
|
|
|
36
36
|
"@azure/storage-blob": "^12.26.0",
|
|
37
37
|
"@duckdb/node-api": "1.5.3-r.2",
|
|
38
38
|
"@google-cloud/storage": "^7.16.0",
|
|
39
|
-
"@malloydata/db-bigquery": "^0.0.
|
|
40
|
-
"@malloydata/db-databricks": "^0.0.
|
|
41
|
-
"@malloydata/db-duckdb": "^0.0.
|
|
42
|
-
"@malloydata/db-mysql": "^0.0.
|
|
43
|
-
"@malloydata/db-postgres": "^0.0.
|
|
44
|
-
"@malloydata/db-publisher": "^0.0.
|
|
45
|
-
"@malloydata/db-snowflake": "^0.0.
|
|
46
|
-
"@malloydata/db-trino": "^0.0.
|
|
47
|
-
"@malloydata/malloy": "^0.0.
|
|
48
|
-
"@malloydata/malloy-sql": "^0.0.
|
|
49
|
-
"@malloydata/render-validator": "^0.0.
|
|
39
|
+
"@malloydata/db-bigquery": "^0.0.422",
|
|
40
|
+
"@malloydata/db-databricks": "^0.0.422",
|
|
41
|
+
"@malloydata/db-duckdb": "^0.0.422",
|
|
42
|
+
"@malloydata/db-mysql": "^0.0.422",
|
|
43
|
+
"@malloydata/db-postgres": "^0.0.422",
|
|
44
|
+
"@malloydata/db-publisher": "^0.0.422",
|
|
45
|
+
"@malloydata/db-snowflake": "^0.0.422",
|
|
46
|
+
"@malloydata/db-trino": "^0.0.422",
|
|
47
|
+
"@malloydata/malloy": "^0.0.422",
|
|
48
|
+
"@malloydata/malloy-sql": "^0.0.422",
|
|
49
|
+
"@malloydata/render-validator": "^0.0.422",
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.13.2",
|
|
51
51
|
"@opentelemetry/api": "^1.9.0",
|
|
52
52
|
"@opentelemetry/auto-instrumentations-node": "^0.57.0",
|
|
@@ -85,6 +85,78 @@ describe("MaterializationController.createMaterialization validation", () => {
|
|
|
85
85
|
expect(await parse({ buildInstructions: null })).toEqual({});
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
+
it("parses referenceManifest and strictUpstreams alongside sources", async () => {
|
|
89
|
+
const parsed = await parse({
|
|
90
|
+
buildInstructions: {
|
|
91
|
+
sources: [
|
|
92
|
+
{
|
|
93
|
+
sourceEntityId: "b2",
|
|
94
|
+
materializedTableId: "mt-2",
|
|
95
|
+
physicalTableName: "downstream_v1",
|
|
96
|
+
realization: "COPY",
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
referenceManifest: [
|
|
100
|
+
{ sourceEntityId: "b1", physicalTableName: "upstream_table" },
|
|
101
|
+
],
|
|
102
|
+
strictUpstreams: true,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
expect(parsed).toEqual({
|
|
106
|
+
buildInstructions: [
|
|
107
|
+
{
|
|
108
|
+
sourceEntityId: "b2",
|
|
109
|
+
sourceID: undefined,
|
|
110
|
+
materializedTableId: "mt-2",
|
|
111
|
+
physicalTableName: "downstream_v1",
|
|
112
|
+
realization: "COPY",
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
referenceManifest: [
|
|
116
|
+
{ sourceEntityId: "b1", physicalTableName: "upstream_table" },
|
|
117
|
+
],
|
|
118
|
+
strictUpstreams: true,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("rejects a referenceManifest entry missing a required field", async () => {
|
|
123
|
+
const { controller } = build();
|
|
124
|
+
await expect(
|
|
125
|
+
controller.createMaterialization("env", "pkg", {
|
|
126
|
+
buildInstructions: {
|
|
127
|
+
sources: [
|
|
128
|
+
{
|
|
129
|
+
sourceEntityId: "b2",
|
|
130
|
+
materializedTableId: "mt-2",
|
|
131
|
+
physicalTableName: "downstream_v1",
|
|
132
|
+
realization: "COPY",
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
referenceManifest: [{ sourceEntityId: "b1" }],
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
).rejects.toThrow(BadRequestError);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("rejects a non-boolean strictUpstreams", async () => {
|
|
142
|
+
const { controller } = build();
|
|
143
|
+
await expect(
|
|
144
|
+
controller.createMaterialization("env", "pkg", {
|
|
145
|
+
buildInstructions: {
|
|
146
|
+
sources: [
|
|
147
|
+
{
|
|
148
|
+
sourceEntityId: "b2",
|
|
149
|
+
materializedTableId: "mt-2",
|
|
150
|
+
physicalTableName: "downstream_v1",
|
|
151
|
+
realization: "COPY",
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
strictUpstreams: "yes",
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
).rejects.toThrow(BadRequestError);
|
|
158
|
+
});
|
|
159
|
+
|
|
88
160
|
it("rejects buildInstructions without a non-empty sources array", async () => {
|
|
89
161
|
const { controller } = build();
|
|
90
162
|
await expect(
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { BadRequestError } from "../errors";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BuildInstruction,
|
|
4
|
+
ManifestReference,
|
|
5
|
+
} from "../storage/DatabaseInterface";
|
|
3
6
|
import { MaterializationService } from "../service/materialization_service";
|
|
4
7
|
|
|
5
8
|
export class MaterializationController {
|
|
@@ -21,19 +24,28 @@ export class MaterializationController {
|
|
|
21
24
|
forceRefresh?: boolean;
|
|
22
25
|
sourceNames?: string[];
|
|
23
26
|
buildInstructions?: BuildInstruction[];
|
|
27
|
+
referenceManifest?: ManifestReference[];
|
|
28
|
+
strictUpstreams?: boolean;
|
|
24
29
|
} {
|
|
25
30
|
const result: {
|
|
26
31
|
forceRefresh?: boolean;
|
|
27
32
|
sourceNames?: string[];
|
|
28
33
|
buildInstructions?: BuildInstruction[];
|
|
34
|
+
referenceManifest?: ManifestReference[];
|
|
35
|
+
strictUpstreams?: boolean;
|
|
29
36
|
} = {};
|
|
30
37
|
if (
|
|
31
38
|
body.buildInstructions !== undefined &&
|
|
32
39
|
body.buildInstructions !== null
|
|
33
40
|
) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
41
|
+
const parsed = this.validateBuildInstructions(body.buildInstructions);
|
|
42
|
+
result.buildInstructions = parsed.sources;
|
|
43
|
+
if (parsed.referenceManifest !== undefined) {
|
|
44
|
+
result.referenceManifest = parsed.referenceManifest;
|
|
45
|
+
}
|
|
46
|
+
if (parsed.strictUpstreams !== undefined) {
|
|
47
|
+
result.strictUpstreams = parsed.strictUpstreams;
|
|
48
|
+
}
|
|
37
49
|
}
|
|
38
50
|
if (body.forceRefresh !== undefined) {
|
|
39
51
|
if (typeof body.forceRefresh !== "boolean") {
|
|
@@ -57,22 +69,74 @@ export class MaterializationController {
|
|
|
57
69
|
|
|
58
70
|
/**
|
|
59
71
|
* Validate the orchestrated `buildInstructions` payload (BuildInstructions:
|
|
60
|
-
* `{ sources: BuildInstruction[] }`)
|
|
61
|
-
* the service consumes
|
|
72
|
+
* `{ sources: BuildInstruction[], referenceManifest?, strictUpstreams? }`)
|
|
73
|
+
* into the parts the service consumes: the flattened instruction list, the
|
|
74
|
+
* optional upstream reference manifest, and the strict flag.
|
|
62
75
|
*/
|
|
63
|
-
private validateBuildInstructions(raw: unknown):
|
|
76
|
+
private validateBuildInstructions(raw: unknown): {
|
|
77
|
+
sources: BuildInstruction[];
|
|
78
|
+
referenceManifest?: ManifestReference[];
|
|
79
|
+
strictUpstreams?: boolean;
|
|
80
|
+
} {
|
|
64
81
|
if (typeof raw !== "object" || raw === null) {
|
|
65
82
|
throw new BadRequestError("buildInstructions must be an object");
|
|
66
83
|
}
|
|
67
|
-
const
|
|
84
|
+
const obj = raw as Record<string, unknown>;
|
|
85
|
+
const sources = obj.sources;
|
|
68
86
|
if (!Array.isArray(sources) || sources.length === 0) {
|
|
69
87
|
throw new BadRequestError(
|
|
70
88
|
"buildInstructions requires a non-empty 'sources' array of BuildInstruction",
|
|
71
89
|
);
|
|
72
90
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
91
|
+
const result: {
|
|
92
|
+
sources: BuildInstruction[];
|
|
93
|
+
referenceManifest?: ManifestReference[];
|
|
94
|
+
strictUpstreams?: boolean;
|
|
95
|
+
} = {
|
|
96
|
+
sources: sources.map((instruction) =>
|
|
97
|
+
this.validateInstruction(instruction),
|
|
98
|
+
),
|
|
99
|
+
};
|
|
100
|
+
if (
|
|
101
|
+
obj.referenceManifest !== undefined &&
|
|
102
|
+
obj.referenceManifest !== null
|
|
103
|
+
) {
|
|
104
|
+
if (!Array.isArray(obj.referenceManifest)) {
|
|
105
|
+
throw new BadRequestError(
|
|
106
|
+
"buildInstructions.referenceManifest must be an array of ManifestReference",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
result.referenceManifest = obj.referenceManifest.map((ref) =>
|
|
110
|
+
this.validateManifestReference(ref),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (obj.strictUpstreams !== undefined) {
|
|
114
|
+
if (typeof obj.strictUpstreams !== "boolean") {
|
|
115
|
+
throw new BadRequestError(
|
|
116
|
+
"buildInstructions.strictUpstreams must be a boolean",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
result.strictUpstreams = obj.strictUpstreams;
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private validateManifestReference(raw: unknown): ManifestReference {
|
|
125
|
+
if (typeof raw !== "object" || raw === null) {
|
|
126
|
+
throw new BadRequestError("Each manifest reference must be an object");
|
|
127
|
+
}
|
|
128
|
+
const ref = raw as Record<string, unknown>;
|
|
129
|
+
for (const field of ["sourceEntityId", "physicalTableName"] as const) {
|
|
130
|
+
if (typeof ref[field] !== "string") {
|
|
131
|
+
throw new BadRequestError(
|
|
132
|
+
`Manifest reference is missing required string field '${field}'`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
sourceEntityId: ref.sourceEntityId as string,
|
|
138
|
+
physicalTableName: ref.physicalTableName as string,
|
|
139
|
+
};
|
|
76
140
|
}
|
|
77
141
|
|
|
78
142
|
private validateInstruction(raw: unknown): BuildInstruction {
|