@malloy-publisher/server 0.0.226 → 0.0.227
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/README.md +2 -11
- package/dist/package_load_worker.mjs +86 -24
- package/dist/server.mjs +655 -6994
- package/package.json +1 -4
- package/src/health.ts +3 -8
- package/src/mcp/error_messages.ts +8 -37
- package/src/mcp/handler_utils.ts +10 -129
- package/src/mcp/mcp_constants.ts +0 -16
- package/src/mcp/{agent_server.protocol.spec.ts → server.protocol.spec.ts} +14 -12
- package/src/mcp/server.ts +29 -37
- package/src/mcp/skills/build_skills_bundle.ts +1 -1
- package/src/mcp/skills/skills_bundle.json +1 -1
- package/src/mcp/tools/docs_search_tool.ts +1 -1
- package/src/mcp/tools/execute_query_tool.ts +2 -2
- package/src/mcp/tools/get_context_eval.ts +3 -3
- package/src/mcp/tools/get_context_tool.spec.ts +196 -1
- package/src/mcp/tools/get_context_tool.ts +165 -67
- package/src/package_load/package_load_pool.ts +3 -0
- package/src/package_load/package_load_worker.ts +68 -24
- package/src/package_load/protocol.ts +16 -0
- package/src/package_load/rpc_wait_accountant.spec.ts +109 -0
- package/src/package_load/rpc_wait_accountant.ts +76 -0
- package/src/package_load_metrics.spec.ts +114 -0
- package/src/package_load_metrics.ts +127 -0
- package/src/pg_helpers.spec.ts +2 -206
- package/src/pg_helpers.ts +4 -120
- package/src/server.ts +0 -16
- package/src/service/environment.ts +1 -1
- package/src/service/package.ts +82 -42
- package/src/test_helpers/metrics_harness.ts +40 -0
- package/tests/harness/mcp_test_setup.ts +1 -1
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +7 -31
- package/tests/integration/watch-mode/watch_mode.integration.spec.ts +0 -6
- package/dxt/malloy_bridge.py +0 -354
- package/dxt/manifest.json +0 -22
- package/src/dto/connection.dto.spec.ts +0 -186
- package/src/dto/connection.dto.ts +0 -308
- package/src/dto/index.ts +0 -2
- package/src/dto/package.dto.spec.ts +0 -42
- package/src/dto/package.dto.ts +0 -27
- package/src/dto/validate.spec.ts +0 -76
- package/src/dto/validate.ts +0 -31
- package/src/ducklake_version.spec.ts +0 -43
- package/src/ducklake_version.ts +0 -26
- package/src/mcp/agent_server.spec.ts +0 -18
- package/src/mcp/agent_server.ts +0 -144
- package/src/mcp/prompts/handlers.ts +0 -84
- package/src/mcp/prompts/index.ts +0 -11
- package/src/mcp/prompts/prompt_definitions.ts +0 -160
- package/src/mcp/prompts/prompt_service.ts +0 -67
- package/src/mcp/prompts/utils.ts +0 -62
- package/src/mcp/resource_metadata.ts +0 -47
- package/src/mcp/resources/environment_resource.ts +0 -187
- package/src/mcp/resources/model_resource.ts +0 -155
- package/src/mcp/resources/notebook_resource.ts +0 -137
- package/src/mcp/resources/package_resource.ts +0 -373
- package/src/mcp/resources/query_resource.ts +0 -122
- package/src/mcp/resources/source_resource.ts +0 -141
- package/src/mcp/resources/view_resource.ts +0 -136
- package/src/mcp/tools/discovery_tools.ts +0 -280
- package/src/storage/BaseRepository.ts +0 -31
- package/src/storage/StorageManager.mock.ts +0 -50
- package/tests/harness/e2e.ts +0 -96
- package/tests/harness/mocks.ts +0 -39
- package/tests/harness/uris.ts +0 -31
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -655
- package/tests/integration/mcp/setup.spec.ts +0 -5
- package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
- package/tests/unit/mcp/prompt_happy.test.ts +0 -51
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import { Type } from "class-transformer";
|
|
2
|
-
import {
|
|
3
|
-
IsDefined,
|
|
4
|
-
IsEnum,
|
|
5
|
-
IsArray,
|
|
6
|
-
IsNumber,
|
|
7
|
-
IsOptional,
|
|
8
|
-
IsString,
|
|
9
|
-
ValidateIf,
|
|
10
|
-
ValidateNested,
|
|
11
|
-
} from "class-validator";
|
|
12
|
-
import "reflect-metadata";
|
|
13
|
-
import { components } from "../api";
|
|
14
|
-
import { ApiConnection } from "../service/model";
|
|
15
|
-
|
|
16
|
-
type AttachedDatabase = components["schemas"]["AttachedDatabase"];
|
|
17
|
-
|
|
18
|
-
export class PostgresConnectionDto {
|
|
19
|
-
@IsOptional()
|
|
20
|
-
@IsString()
|
|
21
|
-
host?: string;
|
|
22
|
-
|
|
23
|
-
@IsOptional()
|
|
24
|
-
@IsNumber()
|
|
25
|
-
port?: number;
|
|
26
|
-
|
|
27
|
-
@IsOptional()
|
|
28
|
-
@IsString()
|
|
29
|
-
databaseName?: string;
|
|
30
|
-
|
|
31
|
-
@IsOptional()
|
|
32
|
-
@IsString()
|
|
33
|
-
userName?: string;
|
|
34
|
-
|
|
35
|
-
@IsOptional()
|
|
36
|
-
@IsString()
|
|
37
|
-
password?: string;
|
|
38
|
-
|
|
39
|
-
@IsOptional()
|
|
40
|
-
@IsString()
|
|
41
|
-
connectionString?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export class MysqlConnectionDto {
|
|
45
|
-
@IsOptional()
|
|
46
|
-
@IsString()
|
|
47
|
-
host?: string;
|
|
48
|
-
|
|
49
|
-
@IsOptional()
|
|
50
|
-
@IsNumber()
|
|
51
|
-
port?: number;
|
|
52
|
-
|
|
53
|
-
@IsOptional()
|
|
54
|
-
@IsString()
|
|
55
|
-
database?: string;
|
|
56
|
-
|
|
57
|
-
@IsOptional()
|
|
58
|
-
@IsString()
|
|
59
|
-
user?: string;
|
|
60
|
-
|
|
61
|
-
@IsOptional()
|
|
62
|
-
@IsString()
|
|
63
|
-
password?: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export class BigqueryConnectionDto {
|
|
67
|
-
@IsOptional()
|
|
68
|
-
@IsString()
|
|
69
|
-
defaultProjectId?: string;
|
|
70
|
-
|
|
71
|
-
@IsOptional()
|
|
72
|
-
@IsString()
|
|
73
|
-
billingProjectId?: string;
|
|
74
|
-
|
|
75
|
-
@IsOptional()
|
|
76
|
-
@IsString()
|
|
77
|
-
location?: string;
|
|
78
|
-
|
|
79
|
-
@IsOptional()
|
|
80
|
-
@IsString()
|
|
81
|
-
serviceAccountKeyJson?: string;
|
|
82
|
-
|
|
83
|
-
@IsOptional()
|
|
84
|
-
@IsString()
|
|
85
|
-
maximumBytesBilled?: string;
|
|
86
|
-
|
|
87
|
-
@IsOptional()
|
|
88
|
-
@IsString()
|
|
89
|
-
queryTimeoutMilliseconds?: string;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export class SnowflakeConnectionDto {
|
|
93
|
-
@IsOptional()
|
|
94
|
-
@IsString()
|
|
95
|
-
account?: string;
|
|
96
|
-
|
|
97
|
-
@IsOptional()
|
|
98
|
-
@IsString()
|
|
99
|
-
username?: string;
|
|
100
|
-
|
|
101
|
-
@IsOptional()
|
|
102
|
-
@IsString()
|
|
103
|
-
password?: string;
|
|
104
|
-
|
|
105
|
-
@IsOptional()
|
|
106
|
-
@IsString()
|
|
107
|
-
privateKey?: string;
|
|
108
|
-
|
|
109
|
-
@IsOptional()
|
|
110
|
-
@IsString()
|
|
111
|
-
privateKeyPass?: string;
|
|
112
|
-
|
|
113
|
-
@IsOptional()
|
|
114
|
-
@IsString()
|
|
115
|
-
warehouse?: string;
|
|
116
|
-
|
|
117
|
-
@IsOptional()
|
|
118
|
-
@IsString()
|
|
119
|
-
database?: string;
|
|
120
|
-
|
|
121
|
-
@IsOptional()
|
|
122
|
-
@IsString()
|
|
123
|
-
schema?: string;
|
|
124
|
-
|
|
125
|
-
@IsOptional()
|
|
126
|
-
@IsString()
|
|
127
|
-
role?: string;
|
|
128
|
-
|
|
129
|
-
@IsOptional()
|
|
130
|
-
@IsNumber()
|
|
131
|
-
responseTimeoutMilliseconds?: number;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export class DatabricksConnectionDto {
|
|
135
|
-
@IsOptional()
|
|
136
|
-
@IsString()
|
|
137
|
-
host?: string;
|
|
138
|
-
|
|
139
|
-
@IsOptional()
|
|
140
|
-
@IsString()
|
|
141
|
-
path?: string;
|
|
142
|
-
|
|
143
|
-
@IsOptional()
|
|
144
|
-
@IsString()
|
|
145
|
-
token?: string;
|
|
146
|
-
|
|
147
|
-
@IsOptional()
|
|
148
|
-
@IsString()
|
|
149
|
-
oauthClientId?: string;
|
|
150
|
-
|
|
151
|
-
@IsOptional()
|
|
152
|
-
@IsString()
|
|
153
|
-
oauthClientSecret?: string;
|
|
154
|
-
|
|
155
|
-
@IsOptional()
|
|
156
|
-
@IsString()
|
|
157
|
-
defaultCatalog?: string;
|
|
158
|
-
|
|
159
|
-
@IsOptional()
|
|
160
|
-
@IsString()
|
|
161
|
-
defaultSchema?: string;
|
|
162
|
-
|
|
163
|
-
@IsOptional()
|
|
164
|
-
@IsString()
|
|
165
|
-
setupSQL?: string;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export class TrinoConnectionDto {
|
|
169
|
-
@IsOptional()
|
|
170
|
-
@IsString()
|
|
171
|
-
server?: string;
|
|
172
|
-
|
|
173
|
-
@IsOptional()
|
|
174
|
-
@IsString()
|
|
175
|
-
port?: number;
|
|
176
|
-
|
|
177
|
-
@IsOptional()
|
|
178
|
-
@IsString()
|
|
179
|
-
catalog?: string;
|
|
180
|
-
|
|
181
|
-
@IsOptional()
|
|
182
|
-
@IsString()
|
|
183
|
-
schema?: string;
|
|
184
|
-
|
|
185
|
-
@IsOptional()
|
|
186
|
-
@IsString()
|
|
187
|
-
user?: string;
|
|
188
|
-
|
|
189
|
-
@IsOptional()
|
|
190
|
-
@IsString()
|
|
191
|
-
password?: string;
|
|
192
|
-
|
|
193
|
-
@IsOptional()
|
|
194
|
-
@IsString()
|
|
195
|
-
peakaKey?: string;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export class DuckdbConnectionDto {
|
|
199
|
-
@IsOptional()
|
|
200
|
-
@IsArray()
|
|
201
|
-
attachedDatabases?: AttachedDatabase[];
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export class SshProxyConfigDto {
|
|
205
|
-
@IsString()
|
|
206
|
-
host!: string;
|
|
207
|
-
|
|
208
|
-
@IsOptional()
|
|
209
|
-
@IsNumber()
|
|
210
|
-
port?: number;
|
|
211
|
-
|
|
212
|
-
@IsString()
|
|
213
|
-
username!: string;
|
|
214
|
-
|
|
215
|
-
@IsString()
|
|
216
|
-
privateKey!: string;
|
|
217
|
-
|
|
218
|
-
@IsOptional()
|
|
219
|
-
@IsString()
|
|
220
|
-
privateKeyPass?: string;
|
|
221
|
-
|
|
222
|
-
@IsOptional()
|
|
223
|
-
@IsString()
|
|
224
|
-
hostKey?: string;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export class ConnectionProxyDto {
|
|
228
|
-
@IsEnum(["ssh"])
|
|
229
|
-
type!: "ssh";
|
|
230
|
-
|
|
231
|
-
// ssh config is required for the ssh proxy type (the only type today); keep
|
|
232
|
-
// the conditional so a future proxy type isn't forced to supply `ssh`.
|
|
233
|
-
@ValidateIf((o) => o.type === "ssh")
|
|
234
|
-
@IsDefined()
|
|
235
|
-
@ValidateNested()
|
|
236
|
-
@Type(() => SshProxyConfigDto)
|
|
237
|
-
ssh?: SshProxyConfigDto;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export class ConnectionDto implements ApiConnection {
|
|
241
|
-
@IsOptional()
|
|
242
|
-
@IsString()
|
|
243
|
-
name?: string;
|
|
244
|
-
|
|
245
|
-
@IsOptional()
|
|
246
|
-
@IsEnum([
|
|
247
|
-
"postgres",
|
|
248
|
-
"bigquery",
|
|
249
|
-
"snowflake",
|
|
250
|
-
"trino",
|
|
251
|
-
"databricks",
|
|
252
|
-
"mysql",
|
|
253
|
-
"duckdb",
|
|
254
|
-
"motherduck",
|
|
255
|
-
"ducklake",
|
|
256
|
-
])
|
|
257
|
-
type?:
|
|
258
|
-
| "postgres"
|
|
259
|
-
| "bigquery"
|
|
260
|
-
| "snowflake"
|
|
261
|
-
| "trino"
|
|
262
|
-
| "databricks"
|
|
263
|
-
| "mysql"
|
|
264
|
-
| "duckdb"
|
|
265
|
-
| "motherduck"
|
|
266
|
-
| "ducklake";
|
|
267
|
-
|
|
268
|
-
// Opaque, secret-free data-identity token; carried verbatim (never parsed
|
|
269
|
-
// or re-hashed) into content-addressed source ids. See api-doc.yaml.
|
|
270
|
-
@IsOptional()
|
|
271
|
-
@IsString()
|
|
272
|
-
fingerprint?: string;
|
|
273
|
-
|
|
274
|
-
@IsOptional()
|
|
275
|
-
@ValidateNested()
|
|
276
|
-
@Type(() => PostgresConnectionDto)
|
|
277
|
-
postgresConnection?: PostgresConnectionDto;
|
|
278
|
-
|
|
279
|
-
@IsOptional()
|
|
280
|
-
@ValidateNested()
|
|
281
|
-
@Type(() => BigqueryConnectionDto)
|
|
282
|
-
bigqueryConnection?: BigqueryConnectionDto;
|
|
283
|
-
|
|
284
|
-
@IsOptional()
|
|
285
|
-
@ValidateNested()
|
|
286
|
-
@Type(() => SnowflakeConnectionDto)
|
|
287
|
-
snowflakeConnection?: SnowflakeConnectionDto;
|
|
288
|
-
|
|
289
|
-
@IsOptional()
|
|
290
|
-
@ValidateNested()
|
|
291
|
-
@Type(() => TrinoConnectionDto)
|
|
292
|
-
TrinoConnection?: TrinoConnectionDto;
|
|
293
|
-
|
|
294
|
-
@IsOptional()
|
|
295
|
-
@ValidateNested()
|
|
296
|
-
@Type(() => DatabricksConnectionDto)
|
|
297
|
-
databricksConnection?: DatabricksConnectionDto;
|
|
298
|
-
|
|
299
|
-
@IsOptional()
|
|
300
|
-
@ValidateNested()
|
|
301
|
-
@Type(() => DuckdbConnectionDto)
|
|
302
|
-
duckdbConnection?: DuckdbConnectionDto;
|
|
303
|
-
|
|
304
|
-
@IsOptional()
|
|
305
|
-
@ValidateNested()
|
|
306
|
-
@Type(() => ConnectionProxyDto)
|
|
307
|
-
proxy?: ConnectionProxyDto;
|
|
308
|
-
}
|
package/src/dto/index.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { expect, it, describe } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import { plainToInstance } from "class-transformer";
|
|
4
|
-
import { validate } from "class-validator";
|
|
5
|
-
import { PackageDto } from "./package.dto";
|
|
6
|
-
import { faker } from "@faker-js/faker";
|
|
7
|
-
|
|
8
|
-
describe("dto/package", () => {
|
|
9
|
-
it("should not throw when valid object is provided", async () => {
|
|
10
|
-
const errors = await validate(
|
|
11
|
-
plainToInstance(PackageDto, {
|
|
12
|
-
name: faker.person.firstName(),
|
|
13
|
-
description: faker.person.lastName(),
|
|
14
|
-
}),
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
expect(errors).toHaveLength(0);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("should throw when no name is specified", async () => {
|
|
21
|
-
const errors = await validate(
|
|
22
|
-
plainToInstance(PackageDto, {
|
|
23
|
-
name: "",
|
|
24
|
-
description: faker.person.lastName(),
|
|
25
|
-
}),
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
expect(errors.length).toBeGreaterThan(0);
|
|
29
|
-
expect(errors).toHaveLength(1);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("should throw when no description is specified", async () => {
|
|
33
|
-
const errors = await validate(
|
|
34
|
-
plainToInstance(PackageDto, {
|
|
35
|
-
name: faker.person.firstName(),
|
|
36
|
-
}),
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
expect(errors.length).toBeGreaterThan(0);
|
|
40
|
-
expect(errors).toHaveLength(1);
|
|
41
|
-
});
|
|
42
|
-
});
|
package/src/dto/package.dto.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
IsString,
|
|
3
|
-
IsNotEmpty,
|
|
4
|
-
IsOptional,
|
|
5
|
-
IsArray,
|
|
6
|
-
IsIn,
|
|
7
|
-
} from "class-validator";
|
|
8
|
-
import { ApiPackage } from "../service/package";
|
|
9
|
-
|
|
10
|
-
export class PackageDto implements ApiPackage {
|
|
11
|
-
@IsString()
|
|
12
|
-
@IsNotEmpty()
|
|
13
|
-
name: string;
|
|
14
|
-
|
|
15
|
-
@IsString()
|
|
16
|
-
@IsNotEmpty()
|
|
17
|
-
description: string;
|
|
18
|
-
|
|
19
|
-
@IsOptional()
|
|
20
|
-
@IsArray()
|
|
21
|
-
@IsString({ each: true })
|
|
22
|
-
explores?: string[];
|
|
23
|
-
|
|
24
|
-
@IsOptional()
|
|
25
|
-
@IsIn(["declared", "all"])
|
|
26
|
-
queryableSources?: "declared" | "all";
|
|
27
|
-
}
|
package/src/dto/validate.spec.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { expect, it, describe } from "bun:test";
|
|
2
|
-
import request from "supertest";
|
|
3
|
-
import express from "express";
|
|
4
|
-
import { validateSchema } from "./validate";
|
|
5
|
-
import { PackageDto } from "./package.dto";
|
|
6
|
-
import { faker } from "@faker-js/faker";
|
|
7
|
-
|
|
8
|
-
const app = express();
|
|
9
|
-
app.use(express.json());
|
|
10
|
-
|
|
11
|
-
app.post("/packages", validateSchema(PackageDto), (req, res) => {
|
|
12
|
-
res.status(201).json({ message: "Package created", data: req.body });
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
describe("dto/validate", () => {
|
|
16
|
-
describe("validateSchema", () => {
|
|
17
|
-
it("should pass validation for a valid request", async () => {
|
|
18
|
-
const dummyPayload = {
|
|
19
|
-
name: faker.person.firstName(),
|
|
20
|
-
description: faker.person.lastName(),
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const response = await request(app)
|
|
24
|
-
.post("/packages")
|
|
25
|
-
.send(dummyPayload);
|
|
26
|
-
|
|
27
|
-
expect(response.status).toBe(201);
|
|
28
|
-
expect(response.body.message).toBe("Package created");
|
|
29
|
-
expect(response.body.data).toEqual(dummyPayload);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("should return 400 for missing required fields", async () => {
|
|
33
|
-
const response = await request(app).post("/packages").send({
|
|
34
|
-
name: "",
|
|
35
|
-
description: "",
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
expect(response.status).toBe(400);
|
|
39
|
-
expect(Array.isArray(response.body.errors)).toBe(true);
|
|
40
|
-
|
|
41
|
-
const expectedErrors = [
|
|
42
|
-
{
|
|
43
|
-
property: "name",
|
|
44
|
-
constraints: { isNotEmpty: "name should not be empty" },
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
property: "description",
|
|
48
|
-
constraints: {
|
|
49
|
-
isNotEmpty: "description should not be empty",
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
];
|
|
53
|
-
expect(response.body.errors).toEqual(
|
|
54
|
-
expect.arrayContaining(expectedErrors),
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("should return 400 for invalid description format", async () => {
|
|
59
|
-
const response = await request(app).post("/packages").send({
|
|
60
|
-
name: faker.person.firstName(),
|
|
61
|
-
description: false,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
expect(response.status).toBe(400);
|
|
65
|
-
const expectedErrors = [
|
|
66
|
-
{
|
|
67
|
-
property: "description",
|
|
68
|
-
constraints: { isString: "description must be a string" },
|
|
69
|
-
},
|
|
70
|
-
];
|
|
71
|
-
expect(response.body.errors).toEqual(
|
|
72
|
-
expect.arrayContaining(expectedErrors),
|
|
73
|
-
);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
});
|
package/src/dto/validate.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
import { plainToInstance, ClassConstructor } from "class-transformer";
|
|
3
|
-
import { validate } from "class-validator";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @description validates incoming requests based on the provided dto schema
|
|
7
|
-
* @param dtoClass to validate against
|
|
8
|
-
* @example
|
|
9
|
-
* app.post('/users', validateSchema(UserDto), (req, res) => {
|
|
10
|
-
* res.status(201).json({ message: 'User created', data: req.body });
|
|
11
|
-
* });
|
|
12
|
-
*/
|
|
13
|
-
export function validateSchema<T extends object>(
|
|
14
|
-
dtoClass: ClassConstructor<T>,
|
|
15
|
-
) {
|
|
16
|
-
return async (req: Request, res: Response, next: NextFunction) => {
|
|
17
|
-
const dtoInstance = plainToInstance(dtoClass, req.body);
|
|
18
|
-
const errors = await validate(dtoInstance);
|
|
19
|
-
|
|
20
|
-
if (errors.length > 0) {
|
|
21
|
-
const formattedErrors = errors.map((err) => ({
|
|
22
|
-
property: err.property,
|
|
23
|
-
constraints: err.constraints,
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
return res.status(400).json({ errors: formattedErrors });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
next();
|
|
30
|
-
};
|
|
31
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
isCatalogVersionSupported,
|
|
4
|
-
SUPPORTED_CATALOG_VERSIONS,
|
|
5
|
-
} from "./ducklake_version";
|
|
6
|
-
|
|
7
|
-
describe("ducklake_version", () => {
|
|
8
|
-
describe("SUPPORTED_CATALOG_VERSIONS", () => {
|
|
9
|
-
it("matches the 0.3-line DuckLake extension's internal list", () => {
|
|
10
|
-
expect([...SUPPORTED_CATALOG_VERSIONS]).toEqual([
|
|
11
|
-
"0.1",
|
|
12
|
-
"0.2",
|
|
13
|
-
"0.3-dev1",
|
|
14
|
-
"0.3",
|
|
15
|
-
]);
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe("isCatalogVersionSupported", () => {
|
|
20
|
-
it("accepts each documented 0.3-line version", () => {
|
|
21
|
-
for (const v of SUPPORTED_CATALOG_VERSIONS) {
|
|
22
|
-
expect(isCatalogVersionSupported(v)).toBe(true);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("rejects 1.0-line versions", () => {
|
|
27
|
-
expect(isCatalogVersionSupported("1.0")).toBe(false);
|
|
28
|
-
expect(isCatalogVersionSupported("1.0.0")).toBe(false);
|
|
29
|
-
expect(isCatalogVersionSupported("1.1.0")).toBe(false);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("rejects newer pre-1.0 versions not in the supported list", () => {
|
|
33
|
-
expect(isCatalogVersionSupported("0.4")).toBe(false);
|
|
34
|
-
expect(isCatalogVersionSupported("0.3-dev2")).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("rejects empty and malformed input without throwing", () => {
|
|
38
|
-
expect(isCatalogVersionSupported("")).toBe(false);
|
|
39
|
-
expect(isCatalogVersionSupported("not a version")).toBe(false);
|
|
40
|
-
expect(isCatalogVersionSupported("0.3 ")).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
});
|
package/src/ducklake_version.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// DuckLake catalog format compatibility check. Lives at `src/` root so
|
|
2
|
-
// both `service/` (user-facing connections) and `storage/` (materialization
|
|
3
|
-
// catalog) can pre-flight a catalog's recorded version without bringing in
|
|
4
|
-
// the other layer's helpers. See CLAUDE.md's "Two parallel DuckLake/PG
|
|
5
|
-
// attach paths" note.
|
|
6
|
-
//
|
|
7
|
-
// The Publisher Docker image bakes a specific DuckLake extension version at
|
|
8
|
-
// build time (see Dockerfile). If a catalog's on-disk format version is
|
|
9
|
-
// newer than what the baked extension supports, ATTACH fails deep inside
|
|
10
|
-
// DuckDB with a generic 500. The preflight catches that case and surfaces
|
|
11
|
-
// it as a non-retryable 422 instead.
|
|
12
|
-
|
|
13
|
-
// Versions the currently-baked DuckLake extension (0.3 line, paired with
|
|
14
|
-
// duckdb@1.4.4) can read. When the Publisher upgrades to the DuckLake 1.0
|
|
15
|
-
// line (separate PR, requires duckdb@1.5+), extend this list to include
|
|
16
|
-
// "1.0" and later format versions.
|
|
17
|
-
export const SUPPORTED_CATALOG_VERSIONS: readonly string[] = [
|
|
18
|
-
"0.1",
|
|
19
|
-
"0.2",
|
|
20
|
-
"0.3-dev1",
|
|
21
|
-
"0.3",
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
export function isCatalogVersionSupported(version: string): boolean {
|
|
25
|
-
return SUPPORTED_CATALOG_VERSIONS.includes(version);
|
|
26
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { agentServerInfo, initializeAgentMcpServer } from "./agent_server";
|
|
3
|
-
import type { EnvironmentStore } from "../service/environment_store";
|
|
4
|
-
|
|
5
|
-
describe("agent MCP server", () => {
|
|
6
|
-
it("advertises a distinct server name from the core MCP server", () => {
|
|
7
|
-
expect(agentServerInfo.name).toBe("malloy-publisher-agent-mcp-server");
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("initializes and registers its tools and skill prompts without throwing", () => {
|
|
11
|
-
// The store is captured for later handler calls, not used during
|
|
12
|
-
// registration, so a placeholder is sufficient for this smoke test.
|
|
13
|
-
const server = initializeAgentMcpServer(
|
|
14
|
-
{} as unknown as EnvironmentStore,
|
|
15
|
-
);
|
|
16
|
-
expect(server).toBeTruthy();
|
|
17
|
-
});
|
|
18
|
-
});
|