@malloy-publisher/server 0.0.382-dev → 0.2.0
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.docker.md +145 -20
- package/README.md +65 -12
- package/dist/app/api-doc.html +5 -0
- package/dist/app/api-doc.yaml +3903 -583
- package/dist/app/assets/EnvironmentPage-DiUgLuCH.js +1 -0
- package/dist/app/assets/HomePage-MhhjhnFj.js +1 -0
- package/dist/app/assets/LightMode-BEyvNUzT.js +1 -0
- package/dist/app/assets/MainPage-D4Cfee9o.js +2 -0
- package/dist/app/assets/MaterializationsPage-nGgGqwSN.js +1 -0
- package/dist/app/assets/ModelPage-DLAcEw-F.js +1 -0
- package/dist/app/assets/PackagePage-Df0ikH7w.js +1 -0
- package/dist/app/assets/RouteError-BFQwtroH.js +1 -0
- package/dist/app/assets/ThemeEditorPage-OoskoQYN.js +1 -0
- package/dist/app/assets/WorkbookPage-CpFgvUDC.js +1 -0
- package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-lb28vQkR.es-BoVSOc75.js} +1 -1
- package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
- package/dist/app/assets/index-5eLCcNmP.css +1 -0
- package/dist/app/assets/index-6ypuFH6g.js +1774 -0
- package/dist/app/assets/index-B8pnU_V_.js +615 -0
- package/dist/app/assets/index-BImcHmVP.js +1 -0
- package/dist/app/assets/index-DSoCUF-f.js +2725 -0
- package/dist/app/assets/index-DfqzSItA.js +2 -0
- package/dist/app/assets/index-w_0OQJgZ.js +23 -0
- package/dist/app/index.html +8 -4
- package/dist/cpufeatures-1yrn0vtw.node +0 -0
- package/dist/default-publisher.config.json +23 -0
- package/dist/{instrumentation.js → instrumentation.mjs} +10631 -10573
- package/dist/package_load_worker.mjs +15219 -0
- package/dist/runtime/publisher.js +331 -0
- package/dist/{server.js → server.mjs} +115548 -49590
- package/dist/sshcrypto-vd2k5hq9.node +0 -0
- package/package.json +41 -28
- package/publisher.config.example.bigquery.json +33 -0
- package/publisher.config.example.duckdb.json +23 -0
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -38
- package/dist/app/assets/HomePage-Dn3E4CuB.js +0 -1
- package/dist/app/assets/MainPage-BzB3yoqi.js +0 -2
- package/dist/app/assets/ModelPage-C9O_sAXT.js +0 -1
- package/dist/app/assets/PackagePage-DcxKEjBX.js +0 -1
- package/dist/app/assets/ProjectPage-BDj307rF.js +0 -1
- package/dist/app/assets/RouteError-DAShbVCG.js +0 -1
- package/dist/app/assets/WorkbookPage-Cs_XYEaB.js +0 -1
- package/dist/app/assets/index-15BOvhp0.js +0 -456
- package/dist/app/assets/index-Bb2jqquW.js +0 -676
- package/dist/app/assets/index-CMlGQMcl.css +0 -1
- package/dist/app/assets/index-D68X76-7.js +0 -1276
- package/dist/app/assets/index.umd-DGBekgSu.js +0 -1145
- package/dxt/malloy_bridge.py +0 -354
- package/dxt/manifest.json +0 -22
- package/eslint.config.mjs +0 -76
- package/k6-tests/README.md +0 -144
- package/k6-tests/bun.lock +0 -797
- package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
- package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
- package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
- package/k6-tests/load-test/load-test-crud.ts +0 -142
- package/k6-tests/load-test/load-test.ts +0 -784
- package/k6-tests/package.json +0 -27
- package/k6-tests/scripts/run-with-otel.sh +0 -50
- package/k6-tests/smoke-test/smoke-test.ts +0 -158
- package/k6-tests/tsconfig.json +0 -29
- package/k6-tests/utils/client_factory.ts +0 -128
- package/k6-tests/utils/common.ts +0 -489
- package/k6-tests/utils/logger.ts +0 -62
- package/k6-tests/utils/types.d.ts +0 -35
- package/malloy_mcp.dxt +0 -0
- package/publisher.config.json +0 -33
- package/src/config.spec.ts +0 -850
- package/src/config.ts +0 -305
- package/src/constants.ts +0 -10
- package/src/controller/compile.controller.ts +0 -35
- package/src/controller/connection.controller.ts +0 -515
- package/src/controller/database.controller.ts +0 -21
- package/src/controller/manifest.controller.ts +0 -29
- package/src/controller/materialization.controller.ts +0 -125
- package/src/controller/model.controller.ts +0 -107
- package/src/controller/package.controller.ts +0 -182
- package/src/controller/query.controller.ts +0 -60
- package/src/controller/watch-mode.controller.ts +0 -98
- package/src/data_styles.ts +0 -71
- package/src/dto/connection.dto.spec.ts +0 -131
- package/src/dto/connection.dto.ts +0 -174
- package/src/dto/index.ts +0 -2
- package/src/dto/package.dto.spec.ts +0 -42
- package/src/dto/package.dto.ts +0 -12
- package/src/dto/validate.spec.ts +0 -76
- package/src/dto/validate.ts +0 -31
- package/src/errors.ts +0 -116
- package/src/health.ts +0 -189
- package/src/instrumentation.ts +0 -148
- package/src/logger.ts +0 -154
- package/src/mcp/error_messages.ts +0 -199
- package/src/mcp/handler_utils.ts +0 -239
- package/src/mcp/mcp_constants.ts +0 -22
- 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/model_resource.ts +0 -153
- package/src/mcp/resources/notebook_resource.ts +0 -137
- package/src/mcp/resources/package_resource.ts +0 -370
- package/src/mcp/resources/project_resource.ts +0 -184
- package/src/mcp/resources/query_resource.ts +0 -117
- package/src/mcp/resources/source_resource.ts +0 -141
- package/src/mcp/resources/view_resource.ts +0 -136
- package/src/mcp/server.ts +0 -62
- package/src/mcp/tools/discovery_tools.ts +0 -258
- package/src/mcp/tools/execute_query_tool.ts +0 -265
- package/src/server.ts +0 -1195
- package/src/service/connection.spec.ts +0 -1331
- package/src/service/connection.ts +0 -1511
- package/src/service/connection_service.spec.ts +0 -641
- package/src/service/connection_service.ts +0 -181
- package/src/service/db_utils.spec.ts +0 -714
- package/src/service/db_utils.ts +0 -1227
- package/src/service/filter.spec.ts +0 -447
- package/src/service/filter.ts +0 -337
- package/src/service/filter_integration.spec.ts +0 -825
- package/src/service/gcs_s3_utils.ts +0 -427
- package/src/service/manifest_service.spec.ts +0 -201
- package/src/service/manifest_service.ts +0 -106
- package/src/service/materialization_service.spec.ts +0 -648
- package/src/service/materialization_service.ts +0 -929
- package/src/service/materialized_table_gc.spec.ts +0 -383
- package/src/service/materialized_table_gc.ts +0 -279
- package/src/service/model.spec.ts +0 -257
- package/src/service/model.ts +0 -1069
- package/src/service/package.spec.ts +0 -236
- package/src/service/package.ts +0 -463
- package/src/service/project.ts +0 -681
- package/src/service/project_compile.spec.ts +0 -197
- package/src/service/project_store.spec.ts +0 -859
- package/src/service/project_store.ts +0 -1531
- package/src/service/quoting.ts +0 -41
- package/src/service/resolve_project.ts +0 -13
- package/src/storage/BaseRepository.ts +0 -31
- package/src/storage/DatabaseInterface.ts +0 -180
- package/src/storage/StorageManager.mock.ts +0 -59
- package/src/storage/StorageManager.ts +0 -210
- package/src/storage/duckdb/ConnectionRepository.ts +0 -141
- package/src/storage/duckdb/DuckDBConnection.ts +0 -177
- package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
- package/src/storage/duckdb/DuckDBRepository.ts +0 -224
- package/src/storage/duckdb/ManifestRepository.ts +0 -119
- package/src/storage/duckdb/MaterializationRepository.ts +0 -249
- package/src/storage/duckdb/PackageRepository.ts +0 -135
- package/src/storage/duckdb/ProjectRepository.ts +0 -148
- package/src/storage/duckdb/manifest_store.spec.ts +0 -133
- package/src/storage/duckdb/schema.ts +0 -147
- package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -146
- package/src/utils.ts +0 -13
- package/tests/fixtures/persist-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-test/persist_test.malloy +0 -11
- package/tests/fixtures/persist-test/publisher.json +0 -5
- package/tests/fixtures/publisher.config.json +0 -15
- package/tests/harness/e2e.ts +0 -96
- package/tests/harness/mcp_test_setup.ts +0 -344
- package/tests/harness/mocks.ts +0 -37
- package/tests/harness/rest_e2e.ts +0 -68
- package/tests/harness/uris.ts +0 -31
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -470
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -434
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -663
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -122
- package/tests/integration/mcp/setup.spec.ts +0 -5
- package/tests/unit/duckdb/attached_databases.test.ts +0 -1132
- package/tests/unit/ducklake/ducklake.test.ts +0 -950
- package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
- package/tests/unit/mcp/prompt_happy.test.ts +0 -51
- package/tsconfig.json +0 -32
package/src/instrumentation.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { metrics } from "@opentelemetry/api";
|
|
2
|
-
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
3
|
-
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
|
|
4
|
-
import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
|
|
5
|
-
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
6
|
-
import {
|
|
7
|
-
ExpressInstrumentation,
|
|
8
|
-
ExpressLayerType,
|
|
9
|
-
} from "@opentelemetry/instrumentation-express";
|
|
10
|
-
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
11
|
-
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
12
|
-
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
13
|
-
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
14
|
-
import {
|
|
15
|
-
ATTR_SERVICE_NAME,
|
|
16
|
-
ATTR_SERVICE_VERSION,
|
|
17
|
-
} from "@opentelemetry/semantic-conventions";
|
|
18
|
-
import type { NextFunction, Request, Response } from "express";
|
|
19
|
-
import { logger } from "./logger";
|
|
20
|
-
|
|
21
|
-
let prometheusExporter: PrometheusExporter | null = null;
|
|
22
|
-
let sdk: NodeSDK | null = null;
|
|
23
|
-
|
|
24
|
-
export function getPrometheusMetricsHandler() {
|
|
25
|
-
if (!prometheusExporter) {
|
|
26
|
-
throw new Error("Prometheus exporter not initialized");
|
|
27
|
-
}
|
|
28
|
-
return (req: Request, res: Response) => {
|
|
29
|
-
prometheusExporter!.getMetricsRequestHandler(req, res);
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Shuts down the OpenTelemetry SDK gracefully.
|
|
35
|
-
* Should be called during application shutdown.
|
|
36
|
-
*/
|
|
37
|
-
export async function shutdownSDK(): Promise<void> {
|
|
38
|
-
if (sdk) {
|
|
39
|
-
await sdk.shutdown();
|
|
40
|
-
sdk = null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function instrument() {
|
|
45
|
-
const otelCollectorUrl = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
46
|
-
|
|
47
|
-
prometheusExporter = new PrometheusExporter({
|
|
48
|
-
preventServerStart: true,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const instrumentations = [
|
|
52
|
-
getNodeAutoInstrumentations(),
|
|
53
|
-
new ExpressInstrumentation({
|
|
54
|
-
ignoreLayersType: [ExpressLayerType.MIDDLEWARE],
|
|
55
|
-
ignoreLayers: [/\/health/, /\/metrics/],
|
|
56
|
-
}),
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
sdk = new NodeSDK({
|
|
60
|
-
resource: resourceFromAttributes({
|
|
61
|
-
[ATTR_SERVICE_NAME]: "publisher",
|
|
62
|
-
[ATTR_SERVICE_VERSION]: "1.0.0",
|
|
63
|
-
}),
|
|
64
|
-
autoDetectResources: true,
|
|
65
|
-
metricReader: prometheusExporter,
|
|
66
|
-
instrumentations,
|
|
67
|
-
...(otelCollectorUrl && {
|
|
68
|
-
spanProcessors: [
|
|
69
|
-
new BatchSpanProcessor(
|
|
70
|
-
new OTLPTraceExporter({
|
|
71
|
-
url: `${otelCollectorUrl}/v1/traces`,
|
|
72
|
-
}),
|
|
73
|
-
),
|
|
74
|
-
],
|
|
75
|
-
logRecordProcessors: [
|
|
76
|
-
new BatchLogRecordProcessor(
|
|
77
|
-
new OTLPLogExporter({
|
|
78
|
-
url: `${otelCollectorUrl}/v1/logs`,
|
|
79
|
-
}),
|
|
80
|
-
),
|
|
81
|
-
],
|
|
82
|
-
}),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
sdk.start();
|
|
86
|
-
|
|
87
|
-
if (otelCollectorUrl) {
|
|
88
|
-
logger.info(
|
|
89
|
-
`OpenTelemetry SDK initialized with OTLP collector at ${otelCollectorUrl}`,
|
|
90
|
-
);
|
|
91
|
-
} else {
|
|
92
|
-
logger.info("OpenTelemetry SDK initialized with Prometheus metrics only");
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
instrument();
|
|
97
|
-
|
|
98
|
-
// --- HTTP metrics middleware ---
|
|
99
|
-
|
|
100
|
-
const meter = metrics.getMeter("publisher");
|
|
101
|
-
|
|
102
|
-
const httpRequestDuration = meter.createHistogram(
|
|
103
|
-
"http_server_request_duration_ms",
|
|
104
|
-
{
|
|
105
|
-
description: "Duration of HTTP requests in milliseconds",
|
|
106
|
-
unit: "ms",
|
|
107
|
-
advice: {
|
|
108
|
-
explicitBucketBoundaries: [
|
|
109
|
-
5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 60000,
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
const httpRequestCount = meter.createCounter("http_server_requests_total", {
|
|
116
|
-
description: "Total number of HTTP requests",
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const IGNORED_PATHS = new Set([
|
|
120
|
-
"/health",
|
|
121
|
-
"/health/liveness",
|
|
122
|
-
"/health/readiness",
|
|
123
|
-
"/metrics",
|
|
124
|
-
]);
|
|
125
|
-
|
|
126
|
-
export function httpMetricsMiddleware(
|
|
127
|
-
req: Request,
|
|
128
|
-
res: Response,
|
|
129
|
-
next: NextFunction,
|
|
130
|
-
) {
|
|
131
|
-
const start = performance.now();
|
|
132
|
-
|
|
133
|
-
res.on("finish", () => {
|
|
134
|
-
if (IGNORED_PATHS.has(req.path)) return;
|
|
135
|
-
|
|
136
|
-
const duration = performance.now() - start;
|
|
137
|
-
const attrs = {
|
|
138
|
-
"http.method": req.method,
|
|
139
|
-
"http.route": req.route?.path ?? req.path,
|
|
140
|
-
"http.status_code": res.statusCode,
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
httpRequestDuration.record(duration, attrs);
|
|
144
|
-
httpRequestCount.add(1, attrs);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
next();
|
|
148
|
-
}
|
package/src/logger.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { AxiosError } from "axios";
|
|
2
|
-
import { RequestHandler } from "express";
|
|
3
|
-
import winston from "winston";
|
|
4
|
-
|
|
5
|
-
const isTelemetryEnabled = Boolean(process.env.OTEL_EXPORTER_OTLP_ENDPOINT);
|
|
6
|
-
|
|
7
|
-
// Determine log level from environment variable
|
|
8
|
-
// Valid levels: error, warn, info, verbose, debug, silly
|
|
9
|
-
// Default: 'debug'
|
|
10
|
-
const VALID_LOG_LEVELS = ["error", "warn", "info", "verbose", "debug", "silly"];
|
|
11
|
-
const getLogLevel = (): string => {
|
|
12
|
-
if (process.env.LOG_LEVEL) {
|
|
13
|
-
const logLevel = process.env.LOG_LEVEL.toLowerCase();
|
|
14
|
-
if (VALID_LOG_LEVELS.includes(logLevel)) {
|
|
15
|
-
return logLevel;
|
|
16
|
-
} else {
|
|
17
|
-
console.error(
|
|
18
|
-
`Invalid log level: ${process.env.LOG_LEVEL}. Valid log levels are: ${VALID_LOG_LEVELS.join(", ")}. Defaulting to "debug".`,
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return "debug";
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const logger = winston.createLogger({
|
|
26
|
-
level: getLogLevel(),
|
|
27
|
-
format: isTelemetryEnabled
|
|
28
|
-
? winston.format.combine(
|
|
29
|
-
winston.format.uncolorize(),
|
|
30
|
-
winston.format.timestamp(),
|
|
31
|
-
winston.format.metadata({
|
|
32
|
-
fillExcept: ["message", "level", "timestamp"],
|
|
33
|
-
}),
|
|
34
|
-
winston.format.json(),
|
|
35
|
-
)
|
|
36
|
-
: winston.format.combine(
|
|
37
|
-
winston.format.colorize(),
|
|
38
|
-
winston.format.simple(),
|
|
39
|
-
),
|
|
40
|
-
transports: [new winston.transports.Console()],
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
//-
|
|
44
|
-
/**
|
|
45
|
-
* Extracts the trace ID from a W3C traceparent header.
|
|
46
|
-
* Format: version-trace-id-parent-id-trace-flags
|
|
47
|
-
* Example: 00-81f2264f363f1b5596c84ab29e6be171-83ef39df12ab6bab-01
|
|
48
|
-
*
|
|
49
|
-
* @param traceparent The traceparent header value
|
|
50
|
-
* @returns The trace ID (32 hex characters) or undefined if invalid
|
|
51
|
-
*/
|
|
52
|
-
function extractTraceIdFromTraceparent(
|
|
53
|
-
traceparent: string | undefined,
|
|
54
|
-
): string | undefined {
|
|
55
|
-
if (!traceparent) {
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// format of traceparent can either be: version-traceId-parentId-traceFlags or traceId
|
|
60
|
-
const parts = traceparent.split("-");
|
|
61
|
-
const traceId =
|
|
62
|
-
parts.length >= 2 ? parts[1] : parts.length == 1 ? parts[0] : undefined;
|
|
63
|
-
// Validate that the traceId is 32 hex characters
|
|
64
|
-
if (traceId && traceId.length === 32 && /^[0-9a-fA-F]{32}$/.test(traceId)) {
|
|
65
|
-
return traceId;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const DISABLE_RESPONSE_LOGGING =
|
|
72
|
-
process.env.DISABLE_RESPONSE_LOGGING === "true" ||
|
|
73
|
-
process.env.DISABLE_RESPONSE_LOGGING === "1";
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Format duration in milliseconds to a human-readable string with unit
|
|
77
|
-
* @param durationMs Duration in milliseconds
|
|
78
|
-
* @returns Formatted string with 2 decimal places and unit (s or ms)
|
|
79
|
-
*/
|
|
80
|
-
export function formatDuration(durationMs: number): string {
|
|
81
|
-
// If duration is >= 1000ms, show in seconds, otherwise show in milliseconds
|
|
82
|
-
if (durationMs >= 1000) {
|
|
83
|
-
const seconds = durationMs / 1000;
|
|
84
|
-
return `${seconds.toFixed(2)}s`;
|
|
85
|
-
}
|
|
86
|
-
return `${durationMs.toFixed(2)}ms`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export const loggerMiddleware: RequestHandler = (req, res, next) => {
|
|
90
|
-
const startTime = performance.now();
|
|
91
|
-
const resJson = res.json;
|
|
92
|
-
res.json = (body: unknown) => {
|
|
93
|
-
res.locals.body = body;
|
|
94
|
-
return resJson.call(res, body);
|
|
95
|
-
};
|
|
96
|
-
res.on("finish", () => {
|
|
97
|
-
const endTime = performance.now();
|
|
98
|
-
const durationMs = endTime - startTime;
|
|
99
|
-
|
|
100
|
-
// Extract trace ID from traceparent header if present
|
|
101
|
-
const traceparent = req.headers["traceparent"] as string | undefined;
|
|
102
|
-
const traceId = extractTraceIdFromTraceparent(traceparent);
|
|
103
|
-
|
|
104
|
-
const logMetadata: Record<string, unknown> = {
|
|
105
|
-
statusCode: res.statusCode,
|
|
106
|
-
duration: formatDuration(durationMs),
|
|
107
|
-
payload: req.body,
|
|
108
|
-
params: req.params,
|
|
109
|
-
query: req.query,
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// Only include response body if response logging is enabled
|
|
113
|
-
if (!DISABLE_RESPONSE_LOGGING) {
|
|
114
|
-
logMetadata.response = res.locals.body;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Add traceId to log metadata if present
|
|
118
|
-
if (traceId) {
|
|
119
|
-
logMetadata.traceId = traceId;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Skip logging for metrics and health endpoints to reduce log noise
|
|
123
|
-
if (
|
|
124
|
-
req.url !== "/metrics" &&
|
|
125
|
-
req.url !== "/health" &&
|
|
126
|
-
req.url !== "/health/liveness" &&
|
|
127
|
-
req.url !== "/health/readiness"
|
|
128
|
-
) {
|
|
129
|
-
logger.info(`${req.method} ${req.url}`, logMetadata);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
next();
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
export const logAxiosError = (error: AxiosError) => {
|
|
136
|
-
if (error.response) {
|
|
137
|
-
// The request was made and the server responded with a status code
|
|
138
|
-
// that falls out of the range of 2xx
|
|
139
|
-
logger.error("Axios server-side error", {
|
|
140
|
-
url: error.response.config.url,
|
|
141
|
-
status: error.response.status,
|
|
142
|
-
headers: error.response.headers,
|
|
143
|
-
data: error.response.data,
|
|
144
|
-
});
|
|
145
|
-
} else if (error.request) {
|
|
146
|
-
// The request was made but no response was received
|
|
147
|
-
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
|
148
|
-
// http.ClientRequest in node.js
|
|
149
|
-
logger.error("Axios client-side error", { error: error.request });
|
|
150
|
-
} else {
|
|
151
|
-
// Something happened in setting up the request that triggered an Error
|
|
152
|
-
logger.error("Axios unknown error", { error });
|
|
153
|
-
}
|
|
154
|
-
};
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
export interface ErrorDetails {
|
|
2
|
-
message: string;
|
|
3
|
-
suggestions: string[];
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface InvalidParamDetail {
|
|
7
|
-
name: string;
|
|
8
|
-
reason: string;
|
|
9
|
-
value?: unknown;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function getInvalidParamsError(
|
|
13
|
-
params: InvalidParamDetail[],
|
|
14
|
-
): ErrorDetails {
|
|
15
|
-
const reasons = params
|
|
16
|
-
.map(
|
|
17
|
-
(p) =>
|
|
18
|
-
`${p.name}: ${p.reason}${p.value !== undefined ? ` (value: ${JSON.stringify(p.value)})` : ""}`,
|
|
19
|
-
)
|
|
20
|
-
.join("; ");
|
|
21
|
-
return {
|
|
22
|
-
message: `Invalid parameter(s) provided. ${reasons}`,
|
|
23
|
-
suggestions: [
|
|
24
|
-
"Check the parameter names and values against the prompt's argument definition.",
|
|
25
|
-
"Ensure all required parameters are provided and have the correct data types.",
|
|
26
|
-
"For URI parameters, ensure they are correctly formatted.",
|
|
27
|
-
],
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Generates error details for a resource not found scenario.
|
|
33
|
-
* @param resourceUriOrContext The URI that was not found, or a context string (e.g., "Package 'X'", "Model 'Y' in package 'X'").
|
|
34
|
-
* @returns ErrorDetails object with message and suggestions.
|
|
35
|
-
*/
|
|
36
|
-
export function getNotFoundError(resourceUriOrContext: string): ErrorDetails {
|
|
37
|
-
const baseMessage = `Resource not found: ${resourceUriOrContext}`;
|
|
38
|
-
const suggestions: string[] = [
|
|
39
|
-
`Verify the identifier or URI (${resourceUriOrContext}) is spelled correctly and exists. Check capitalization and path separators.`,
|
|
40
|
-
`If using a URI, ensure it follows the correct format (e.g., malloy://project/...) and includes the right path segments (e.g., /models/, /sources/, /queries/, /views/).`,
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
suggestions.push(
|
|
44
|
-
"Check if the resource exists and is correctly named in your Malloy project structure or the specific model file.",
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
message: baseMessage,
|
|
49
|
-
suggestions: suggestions,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Generates generic error details for internal server errors.
|
|
55
|
-
* @param operation The operation that failed (e.g., 'GetResource', 'ListResources').
|
|
56
|
-
* @param error Optional: The underlying error object or message.
|
|
57
|
-
* @returns ErrorDetails object.
|
|
58
|
-
*/
|
|
59
|
-
export function getInternalError(
|
|
60
|
-
operation: string,
|
|
61
|
-
error?: unknown,
|
|
62
|
-
): ErrorDetails {
|
|
63
|
-
const baseMessage = `An unexpected internal error occurred during ${operation}.`;
|
|
64
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
65
|
-
return {
|
|
66
|
-
message: error ? `${baseMessage}: ${errorMessage}` : baseMessage,
|
|
67
|
-
suggestions: [
|
|
68
|
-
"Try the request again later.",
|
|
69
|
-
"If the problem persists, check server logs or contact support.",
|
|
70
|
-
],
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Generates detailed error information for Malloy compilation or query execution errors.
|
|
76
|
-
* @param operation The operation that failed (e.g., 'GetResource (model)', 'executeQuery').
|
|
77
|
-
* @param modelIdentifier A path or URI identifying the model/notebook involved.
|
|
78
|
-
* @param error The underlying Malloy error object or other error.
|
|
79
|
-
* @returns ErrorDetails object.
|
|
80
|
-
*/
|
|
81
|
-
export function getMalloyErrorDetails(
|
|
82
|
-
operation: string,
|
|
83
|
-
modelIdentifier: string,
|
|
84
|
-
error: unknown,
|
|
85
|
-
): ErrorDetails {
|
|
86
|
-
let baseMessage = `Error during ${operation} for resource '${modelIdentifier}'.`;
|
|
87
|
-
const initialSuggestions: string[] = [
|
|
88
|
-
`Verify the structure and syntax within the Malloy file: ${modelIdentifier}. Check for typos in source, query, or view names (e.g., 'source: my_source is ...', 'query: my_query is ...', 'view: my_view is { ... }'). See Model Structure: https://docs.malloydata.dev/documentation/language/statement`,
|
|
89
|
-
"Ensure all referenced sources, queries, or views are correctly defined within the model or imported files using expected keywords (e.g., 'source:', 'query:', 'view:'). See Sources: https://docs.malloydata.dev/documentation/language/source",
|
|
90
|
-
"Check the database connection configuration used by the model, if applicable.",
|
|
91
|
-
"Consult the Malloy language documentation for syntax rules: https://docs.malloydata.dev/documentation/",
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
let suggestions: string[] = [...initialSuggestions]; // Clone initial suggestions
|
|
95
|
-
let refined = false; // Flag to track if specific suggestions were added
|
|
96
|
-
|
|
97
|
-
// Attempt to extract more specific info if it's a MalloyError or similar
|
|
98
|
-
if (error instanceof Error) {
|
|
99
|
-
// Prepend the specific error message
|
|
100
|
-
baseMessage = `Error during ${operation} for resource '${modelIdentifier}': ${error.message}`;
|
|
101
|
-
|
|
102
|
-
// --- Add more specific suggestions based on common Malloy error patterns ---
|
|
103
|
-
// Note: Order matters, check for more specific patterns first.
|
|
104
|
-
|
|
105
|
-
const viewNotFoundMatch = error.message.match(
|
|
106
|
-
/View '([^']+)' not found in source '([^']+)'/i,
|
|
107
|
-
);
|
|
108
|
-
const sourceNotFoundMatch = error.message.match(
|
|
109
|
-
/Source '([^']+)' not found/i,
|
|
110
|
-
);
|
|
111
|
-
const queryNotFoundMatch = error.message.match(
|
|
112
|
-
/Query '([^']+)' not found/i,
|
|
113
|
-
);
|
|
114
|
-
const referenceErrorMatch = error.message.match(
|
|
115
|
-
/Reference to undefined object(?: '([^']+)')?/i,
|
|
116
|
-
);
|
|
117
|
-
const syntaxErrorMatch = error.message.match(
|
|
118
|
-
/no viable alternative at input/i,
|
|
119
|
-
);
|
|
120
|
-
const connectionErrorMatch = error.message.match(
|
|
121
|
-
/Cannot connect to database/i,
|
|
122
|
-
);
|
|
123
|
-
const fieldNotFoundMatch = error.message.match(
|
|
124
|
-
/Field '([^']+)' not found in (source|query|view) '([^']+)'/i,
|
|
125
|
-
);
|
|
126
|
-
const invalidRequestMatch = error.message.match(
|
|
127
|
-
/Invalid query request\\. Query OR queryName must be defined/i,
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
if (viewNotFoundMatch) {
|
|
131
|
-
refined = true;
|
|
132
|
-
const [, viewName, sourceName] = viewNotFoundMatch;
|
|
133
|
-
suggestions.unshift(
|
|
134
|
-
`Suggestion: View '${viewName}' was not found in source '${sourceName}'. Check the view name spelling or try requesting the resource details for the source URI (e.g., 'malloy://.../sources/${sourceName}') to see the list of available views. Views are defined within sources like 'source: ${sourceName} is ... extend { view: ${viewName} is { ... } }'.`,
|
|
135
|
-
);
|
|
136
|
-
} else if (sourceNotFoundMatch) {
|
|
137
|
-
refined = true;
|
|
138
|
-
const [, sourceName] = sourceNotFoundMatch;
|
|
139
|
-
suggestions.unshift(
|
|
140
|
-
`Suggestion: Source '${sourceName}' was not found or could not be accessed. Verify its definition (e.g., \`source: ${sourceName} is table('...')\` or \`duckdb.sql("...")\`) and ensure any associated connections are valid.`,
|
|
141
|
-
`Suggestion: Check the spelling of '${sourceName}'. You can list sources in the model using ListResources('malloy://.../${modelIdentifier}').`,
|
|
142
|
-
);
|
|
143
|
-
} else if (queryNotFoundMatch) {
|
|
144
|
-
refined = true;
|
|
145
|
-
const [, queryName] = queryNotFoundMatch;
|
|
146
|
-
suggestions.unshift(
|
|
147
|
-
`Suggestion: Named query '${queryName}' was not found. Verify its definition (e.g., \`query: ${queryName} is source_name -> { ... }\`) within the model '${modelIdentifier}'.`,
|
|
148
|
-
`Suggestion: Check the spelling of '${queryName}'. Ensure it's a named query (defined with \`query:\`), not a view. You can list named queries using ListResources('malloy://.../${modelIdentifier}').`,
|
|
149
|
-
);
|
|
150
|
-
} else if (fieldNotFoundMatch) {
|
|
151
|
-
refined = true;
|
|
152
|
-
const [, fieldName, fieldContextType, fieldContextName] =
|
|
153
|
-
fieldNotFoundMatch;
|
|
154
|
-
suggestions.unshift(
|
|
155
|
-
`Suggestion: Field '${fieldName}' was not found in ${fieldContextType} '${fieldContextName}'. Check the spelling of the field name within the definition of '${fieldContextName}'.`,
|
|
156
|
-
`Suggestion: Ensure the field is defined directly or inherited correctly in the '${fieldContextName}' ${fieldContextType}. You can inspect the ${fieldContextType} definition using GetResource.`,
|
|
157
|
-
);
|
|
158
|
-
} else if (referenceErrorMatch) {
|
|
159
|
-
refined = true;
|
|
160
|
-
const objectName = referenceErrorMatch[1] || "[unknown object]";
|
|
161
|
-
suggestions.unshift(
|
|
162
|
-
`Suggestion: Check if the referenced object '${objectName}' (source, query, view, field, dimension, measure) is defined correctly and spelled correctly (e.g., 'source: my_source is ...', 'query: my_query is ...'). Ensure it's defined *before* being used in the model file.`,
|
|
163
|
-
);
|
|
164
|
-
} else if (syntaxErrorMatch) {
|
|
165
|
-
refined = true;
|
|
166
|
-
suggestions.unshift(
|
|
167
|
-
"Suggestion: There seems to be a syntax error near the location indicated in the error message. Check for misplaced/missing keywords (like 'is', '->', 'extend', 'where', 'aggregate', 'group_by', 'nest'), commas, brackets '{ }', or parentheses '( )'. See Query Syntax: https://docs.malloydata.dev/documentation/language/query",
|
|
168
|
-
);
|
|
169
|
-
} else if (connectionErrorMatch) {
|
|
170
|
-
refined = true;
|
|
171
|
-
suggestions.unshift(
|
|
172
|
-
"Suggestion: Verify the database connection details (credentials, host, database name, connection name in Malloy) associated with this model.",
|
|
173
|
-
);
|
|
174
|
-
suggestions.push(
|
|
175
|
-
"Ensure the database server is running and accessible from the publisher server.",
|
|
176
|
-
);
|
|
177
|
-
} else if (invalidRequestMatch) {
|
|
178
|
-
refined = true;
|
|
179
|
-
suggestions.unshift(
|
|
180
|
-
"Suggestion: This error can occur when specifying a view using 'sourceName' and 'queryName'. Try providing the full query string directly in the 'query' parameter instead (e.g., 'query': 'run: your_source_name->your_view_name').",
|
|
181
|
-
"Suggestion: Also verify you are providing either the 'query' parameter OR the correct combination of 'queryName' (and optionally 'sourceName' for views).",
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// If no specific pattern matched, keep the initial suggestions
|
|
186
|
-
if (!refined) {
|
|
187
|
-
suggestions = initialSuggestions;
|
|
188
|
-
}
|
|
189
|
-
} else {
|
|
190
|
-
// Generic message if it's not a standard Error object
|
|
191
|
-
baseMessage += `: ${String(error)}`;
|
|
192
|
-
suggestions = initialSuggestions; // Use initial for non-errors too
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
message: baseMessage,
|
|
197
|
-
suggestions: suggestions,
|
|
198
|
-
};
|
|
199
|
-
}
|