@things-factory/integration-mlflow 9.1.19

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 (68) hide show
  1. package/client/bootstrap.ts +41 -0
  2. package/client/index.ts +2 -0
  3. package/client/pages/mlflow-experiments-page.ts +45 -0
  4. package/client/pages/mlflow-models-page.ts +45 -0
  5. package/client/pages/mlflow-runs-page.ts +45 -0
  6. package/client/pages/mlflow-viewer-page.ts +74 -0
  7. package/client/route.ts +19 -0
  8. package/client/tsconfig.json +11 -0
  9. package/dist-client/bootstrap.d.ts +1 -0
  10. package/dist-client/bootstrap.js +40 -0
  11. package/dist-client/bootstrap.js.map +1 -0
  12. package/dist-client/index.d.ts +2 -0
  13. package/dist-client/index.js +3 -0
  14. package/dist-client/index.js.map +1 -0
  15. package/dist-client/pages/mlflow-experiments-page.d.ts +5 -0
  16. package/dist-client/pages/mlflow-experiments-page.js +48 -0
  17. package/dist-client/pages/mlflow-experiments-page.js.map +1 -0
  18. package/dist-client/pages/mlflow-models-page.d.ts +5 -0
  19. package/dist-client/pages/mlflow-models-page.js +48 -0
  20. package/dist-client/pages/mlflow-models-page.js.map +1 -0
  21. package/dist-client/pages/mlflow-runs-page.d.ts +5 -0
  22. package/dist-client/pages/mlflow-runs-page.js +48 -0
  23. package/dist-client/pages/mlflow-runs-page.js.map +1 -0
  24. package/dist-client/pages/mlflow-viewer-page.d.ts +12 -0
  25. package/dist-client/pages/mlflow-viewer-page.js +82 -0
  26. package/dist-client/pages/mlflow-viewer-page.js.map +1 -0
  27. package/dist-client/route.d.ts +1 -0
  28. package/dist-client/route.js +17 -0
  29. package/dist-client/route.js.map +1 -0
  30. package/dist-client/tsconfig.tsbuildinfo +1 -0
  31. package/dist-server/index.d.ts +4 -0
  32. package/dist-server/index.js +8 -0
  33. package/dist-server/index.js.map +1 -0
  34. package/dist-server/route.d.ts +5 -0
  35. package/dist-server/route.js +12 -0
  36. package/dist-server/route.js.map +1 -0
  37. package/dist-server/service/experiment/experiment-service.d.ts +19 -0
  38. package/dist-server/service/experiment/experiment-service.js +135 -0
  39. package/dist-server/service/experiment/experiment-service.js.map +1 -0
  40. package/dist-server/service/index.d.ts +10 -0
  41. package/dist-server/service/index.js +19 -0
  42. package/dist-server/service/index.js.map +1 -0
  43. package/dist-server/service/model-registry/model-registry-service.d.ts +31 -0
  44. package/dist-server/service/model-registry/model-registry-service.js +255 -0
  45. package/dist-server/service/model-registry/model-registry-service.js.map +1 -0
  46. package/dist-server/service/run/run-service.d.ts +27 -0
  47. package/dist-server/service/run/run-service.js +214 -0
  48. package/dist-server/service/run/run-service.js.map +1 -0
  49. package/dist-server/tsconfig.tsbuildinfo +1 -0
  50. package/dist-server/types/mlflow-types.d.ts +87 -0
  51. package/dist-server/types/mlflow-types.js +337 -0
  52. package/dist-server/types/mlflow-types.js.map +1 -0
  53. package/dist-server/utils/mlflow-api-client.d.ts +277 -0
  54. package/dist-server/utils/mlflow-api-client.js +201 -0
  55. package/dist-server/utils/mlflow-api-client.js.map +1 -0
  56. package/package.json +45 -0
  57. package/server/index.ts +5 -0
  58. package/server/route.ts +13 -0
  59. package/server/service/experiment/experiment-service.ts +117 -0
  60. package/server/service/index.ts +17 -0
  61. package/server/service/model-registry/model-registry-service.ts +232 -0
  62. package/server/service/run/run-service.ts +177 -0
  63. package/server/tsconfig.json +11 -0
  64. package/server/types/mlflow-types.ts +235 -0
  65. package/server/utils/mlflow-api-client.ts +375 -0
  66. package/things-factory.config.js +13 -0
  67. package/translations/en.json +28 -0
  68. package/translations/ko.json +28 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * MLflow REST API Client
3
+ *
4
+ * Thin wrapper around MLflow REST API for Things-Factory integration
5
+ * Reference: https://mlflow.org/docs/latest/rest-api.html
6
+ */
7
+ declare class MLflowApiClient {
8
+ private baseUrl;
9
+ constructor(baseUrl?: string);
10
+ /**
11
+ * Generic HTTP request wrapper
12
+ */
13
+ private request;
14
+ /**
15
+ * Create a new experiment
16
+ */
17
+ createExperiment(params: {
18
+ name: string;
19
+ artifact_location?: string;
20
+ tags?: Array<{
21
+ key: string;
22
+ value: string;
23
+ }>;
24
+ }): Promise<{
25
+ experiment_id: string;
26
+ }>;
27
+ /**
28
+ * Get experiment by ID
29
+ */
30
+ getExperiment(experimentId: string): Promise<{
31
+ experiment: {
32
+ experiment_id: string;
33
+ name: string;
34
+ artifact_location: string;
35
+ lifecycle_stage: string;
36
+ tags?: Array<{
37
+ key: string;
38
+ value: string;
39
+ }>;
40
+ };
41
+ }>;
42
+ /**
43
+ * Search experiments
44
+ */
45
+ searchExperiments(params?: {
46
+ max_results?: number;
47
+ filter?: string;
48
+ order_by?: string[];
49
+ }): Promise<{
50
+ experiments: Array<{
51
+ experiment_id: string;
52
+ name: string;
53
+ artifact_location: string;
54
+ lifecycle_stage: string;
55
+ tags?: Array<{
56
+ key: string;
57
+ value: string;
58
+ }>;
59
+ }>;
60
+ }>;
61
+ /**
62
+ * Delete experiment
63
+ */
64
+ deleteExperiment(experimentId: string): Promise<void>;
65
+ /**
66
+ * Create a new run
67
+ */
68
+ createRun(params: {
69
+ experiment_id: string;
70
+ start_time?: number;
71
+ tags?: Array<{
72
+ key: string;
73
+ value: string;
74
+ }>;
75
+ }): Promise<{
76
+ run: {
77
+ info: {
78
+ run_id: string;
79
+ experiment_id: string;
80
+ status: string;
81
+ start_time: number;
82
+ artifact_uri: string;
83
+ };
84
+ data: any;
85
+ };
86
+ }>;
87
+ /**
88
+ * Get run by ID
89
+ */
90
+ getRun(runId: string): Promise<{
91
+ run: {
92
+ info: {
93
+ run_id: string;
94
+ experiment_id: string;
95
+ status: string;
96
+ start_time: number;
97
+ end_time?: number;
98
+ artifact_uri: string;
99
+ };
100
+ data: {
101
+ metrics: Array<{
102
+ key: string;
103
+ value: number;
104
+ timestamp: number;
105
+ step: number;
106
+ }>;
107
+ params: Array<{
108
+ key: string;
109
+ value: string;
110
+ }>;
111
+ tags: Array<{
112
+ key: string;
113
+ value: string;
114
+ }>;
115
+ };
116
+ };
117
+ }>;
118
+ /**
119
+ * Search runs
120
+ */
121
+ searchRuns(params: {
122
+ experiment_ids: string[];
123
+ filter?: string;
124
+ max_results?: number;
125
+ order_by?: string[];
126
+ }): Promise<{
127
+ runs: Array<{
128
+ info: {
129
+ run_id: string;
130
+ experiment_id: string;
131
+ status: string;
132
+ start_time: number;
133
+ end_time?: number;
134
+ artifact_uri: string;
135
+ };
136
+ data: any;
137
+ }>;
138
+ }>;
139
+ /**
140
+ * Log metric for a run
141
+ */
142
+ logMetric(params: {
143
+ run_id: string;
144
+ key: string;
145
+ value: number;
146
+ timestamp?: number;
147
+ step?: number;
148
+ }): Promise<void>;
149
+ /**
150
+ * Log parameter for a run
151
+ */
152
+ logParam(params: {
153
+ run_id: string;
154
+ key: string;
155
+ value: string;
156
+ }): Promise<void>;
157
+ /**
158
+ * Update run status
159
+ */
160
+ updateRun(params: {
161
+ run_id: string;
162
+ status?: 'RUNNING' | 'SCHEDULED' | 'FINISHED' | 'FAILED' | 'KILLED';
163
+ end_time?: number;
164
+ }): Promise<void>;
165
+ /**
166
+ * Create registered model
167
+ */
168
+ createRegisteredModel(params: {
169
+ name: string;
170
+ tags?: Array<{
171
+ key: string;
172
+ value: string;
173
+ }>;
174
+ description?: string;
175
+ }): Promise<{
176
+ registered_model: {
177
+ name: string;
178
+ creation_timestamp: number;
179
+ last_updated_timestamp: number;
180
+ };
181
+ }>;
182
+ /**
183
+ * Get registered model
184
+ */
185
+ getRegisteredModel(name: string): Promise<{
186
+ registered_model: {
187
+ name: string;
188
+ creation_timestamp: number;
189
+ last_updated_timestamp: number;
190
+ description?: string;
191
+ tags?: Array<{
192
+ key: string;
193
+ value: string;
194
+ }>;
195
+ latest_versions?: any[];
196
+ };
197
+ }>;
198
+ /**
199
+ * Search registered models
200
+ */
201
+ searchRegisteredModels(params?: {
202
+ filter?: string;
203
+ max_results?: number;
204
+ order_by?: string[];
205
+ }): Promise<{
206
+ registered_models: Array<{
207
+ name: string;
208
+ creation_timestamp: number;
209
+ last_updated_timestamp: number;
210
+ description?: string;
211
+ latest_versions?: any[];
212
+ }>;
213
+ }>;
214
+ /**
215
+ * Create model version
216
+ */
217
+ createModelVersion(params: {
218
+ name: string;
219
+ source: string;
220
+ run_id?: string;
221
+ tags?: Array<{
222
+ key: string;
223
+ value: string;
224
+ }>;
225
+ description?: string;
226
+ }): Promise<{
227
+ model_version: {
228
+ name: string;
229
+ version: string;
230
+ creation_timestamp: number;
231
+ last_updated_timestamp: number;
232
+ source: string;
233
+ status: string;
234
+ };
235
+ }>;
236
+ /**
237
+ * Get model version
238
+ */
239
+ getModelVersion(params: {
240
+ name: string;
241
+ version: string;
242
+ }): Promise<{
243
+ model_version: {
244
+ name: string;
245
+ version: string;
246
+ creation_timestamp: number;
247
+ current_stage: string;
248
+ source: string;
249
+ status: string;
250
+ run_id?: string;
251
+ };
252
+ }>;
253
+ /**
254
+ * Transition model version stage
255
+ */
256
+ transitionModelVersionStage(params: {
257
+ name: string;
258
+ version: string;
259
+ stage: 'None' | 'Staging' | 'Production' | 'Archived';
260
+ archive_existing_versions?: boolean;
261
+ }): Promise<{
262
+ model_version: {
263
+ name: string;
264
+ version: string;
265
+ current_stage: string;
266
+ };
267
+ }>;
268
+ /**
269
+ * Delete model version
270
+ */
271
+ deleteModelVersion(params: {
272
+ name: string;
273
+ version: string;
274
+ }): Promise<void>;
275
+ }
276
+ export declare const mlflowApi: MLflowApiClient;
277
+ export { MLflowApiClient };
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ /**
3
+ * MLflow REST API Client
4
+ *
5
+ * Thin wrapper around MLflow REST API for Things-Factory integration
6
+ * Reference: https://mlflow.org/docs/latest/rest-api.html
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.MLflowApiClient = exports.mlflowApi = void 0;
10
+ const env_1 = require("@things-factory/env");
11
+ const MLFLOW_TRACKING_URI = env_1.config.get('mlflowTrackingUri', process.env.MLFLOW_TRACKING_URI || 'http://localhost:5000');
12
+ class MLflowApiClient {
13
+ constructor(baseUrl = MLFLOW_TRACKING_URI) {
14
+ this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash
15
+ }
16
+ /**
17
+ * Generic HTTP request wrapper
18
+ */
19
+ async request(endpoint, options = {}) {
20
+ const url = `${this.baseUrl}${endpoint}`;
21
+ const defaultHeaders = {
22
+ 'Content-Type': 'application/json'
23
+ };
24
+ try {
25
+ const response = await fetch(url, {
26
+ ...options,
27
+ headers: {
28
+ ...defaultHeaders,
29
+ ...options.headers
30
+ }
31
+ });
32
+ if (!response.ok) {
33
+ const errorText = await response.text();
34
+ throw new Error(`MLflow API error: ${response.status} - ${errorText}`);
35
+ }
36
+ return await response.json();
37
+ }
38
+ catch (error) {
39
+ console.error(`MLflow API request failed: ${endpoint}`, error);
40
+ throw error;
41
+ }
42
+ }
43
+ // ========================================================================
44
+ // Experiments API
45
+ // ========================================================================
46
+ /**
47
+ * Create a new experiment
48
+ */
49
+ async createExperiment(params) {
50
+ return this.request('/api/2.0/mlflow/experiments/create', {
51
+ method: 'POST',
52
+ body: JSON.stringify(params)
53
+ });
54
+ }
55
+ /**
56
+ * Get experiment by ID
57
+ */
58
+ async getExperiment(experimentId) {
59
+ return this.request(`/api/2.0/mlflow/experiments/get?experiment_id=${experimentId}`);
60
+ }
61
+ /**
62
+ * Search experiments
63
+ */
64
+ async searchExperiments(params) {
65
+ const queryParams = new URLSearchParams();
66
+ if (params?.max_results)
67
+ queryParams.append('max_results', String(params.max_results));
68
+ if (params?.filter)
69
+ queryParams.append('filter', params.filter);
70
+ return this.request(`/api/2.0/mlflow/experiments/search?${queryParams}`);
71
+ }
72
+ /**
73
+ * Delete experiment
74
+ */
75
+ async deleteExperiment(experimentId) {
76
+ return this.request('/api/2.0/mlflow/experiments/delete', {
77
+ method: 'POST',
78
+ body: JSON.stringify({ experiment_id: experimentId })
79
+ });
80
+ }
81
+ // ========================================================================
82
+ // Runs API
83
+ // ========================================================================
84
+ /**
85
+ * Create a new run
86
+ */
87
+ async createRun(params) {
88
+ return this.request('/api/2.0/mlflow/runs/create', {
89
+ method: 'POST',
90
+ body: JSON.stringify(params)
91
+ });
92
+ }
93
+ /**
94
+ * Get run by ID
95
+ */
96
+ async getRun(runId) {
97
+ return this.request(`/api/2.0/mlflow/runs/get?run_id=${runId}`);
98
+ }
99
+ /**
100
+ * Search runs
101
+ */
102
+ async searchRuns(params) {
103
+ return this.request('/api/2.0/mlflow/runs/search', {
104
+ method: 'POST',
105
+ body: JSON.stringify(params)
106
+ });
107
+ }
108
+ /**
109
+ * Log metric for a run
110
+ */
111
+ async logMetric(params) {
112
+ return this.request('/api/2.0/mlflow/runs/log-metric', {
113
+ method: 'POST',
114
+ body: JSON.stringify(params)
115
+ });
116
+ }
117
+ /**
118
+ * Log parameter for a run
119
+ */
120
+ async logParam(params) {
121
+ return this.request('/api/2.0/mlflow/runs/log-parameter', {
122
+ method: 'POST',
123
+ body: JSON.stringify(params)
124
+ });
125
+ }
126
+ /**
127
+ * Update run status
128
+ */
129
+ async updateRun(params) {
130
+ return this.request('/api/2.0/mlflow/runs/update', {
131
+ method: 'POST',
132
+ body: JSON.stringify(params)
133
+ });
134
+ }
135
+ // ========================================================================
136
+ // Model Registry API
137
+ // ========================================================================
138
+ /**
139
+ * Create registered model
140
+ */
141
+ async createRegisteredModel(params) {
142
+ return this.request('/api/2.0/mlflow/registered-models/create', {
143
+ method: 'POST',
144
+ body: JSON.stringify(params)
145
+ });
146
+ }
147
+ /**
148
+ * Get registered model
149
+ */
150
+ async getRegisteredModel(name) {
151
+ return this.request(`/api/2.0/mlflow/registered-models/get?name=${encodeURIComponent(name)}`);
152
+ }
153
+ /**
154
+ * Search registered models
155
+ */
156
+ async searchRegisteredModels(params) {
157
+ const queryParams = new URLSearchParams();
158
+ if (params?.filter)
159
+ queryParams.append('filter', params.filter);
160
+ if (params?.max_results)
161
+ queryParams.append('max_results', String(params.max_results));
162
+ return this.request(`/api/2.0/mlflow/registered-models/search?${queryParams}`);
163
+ }
164
+ /**
165
+ * Create model version
166
+ */
167
+ async createModelVersion(params) {
168
+ return this.request('/api/2.0/mlflow/model-versions/create', {
169
+ method: 'POST',
170
+ body: JSON.stringify(params)
171
+ });
172
+ }
173
+ /**
174
+ * Get model version
175
+ */
176
+ async getModelVersion(params) {
177
+ return this.request(`/api/2.0/mlflow/model-versions/get?name=${encodeURIComponent(params.name)}&version=${params.version}`);
178
+ }
179
+ /**
180
+ * Transition model version stage
181
+ */
182
+ async transitionModelVersionStage(params) {
183
+ return this.request('/api/2.0/mlflow/model-versions/transition-stage', {
184
+ method: 'POST',
185
+ body: JSON.stringify(params)
186
+ });
187
+ }
188
+ /**
189
+ * Delete model version
190
+ */
191
+ async deleteModelVersion(params) {
192
+ return this.request('/api/2.0/mlflow/model-versions/delete', {
193
+ method: 'DELETE',
194
+ body: JSON.stringify(params)
195
+ });
196
+ }
197
+ }
198
+ exports.MLflowApiClient = MLflowApiClient;
199
+ // Export singleton instance
200
+ exports.mlflowApi = new MLflowApiClient();
201
+ //# sourceMappingURL=mlflow-api-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mlflow-api-client.js","sourceRoot":"","sources":["../../server/utils/mlflow-api-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MLflow REST API Client\n *\n * Thin wrapper around MLflow REST API for Things-Factory integration\n * Reference: https://mlflow.org/docs/latest/rest-api.html\n */\n\nimport { config } from '@things-factory/env'\n\nconst MLFLOW_TRACKING_URI = config.get('mlflowTrackingUri', process.env.MLFLOW_TRACKING_URI || 'http://localhost:5000')\n\ninterface MLflowResponse<T> {\n data?: T\n error?: string\n}\n\nclass MLflowApiClient {\n private baseUrl: string\n\n constructor(baseUrl: string = MLFLOW_TRACKING_URI) {\n this.baseUrl = baseUrl.replace(/\\/$/, '') // Remove trailing slash\n }\n\n /**\n * Generic HTTP request wrapper\n */\n private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`\n\n const defaultHeaders = {\n 'Content-Type': 'application/json'\n }\n\n try {\n const response = await fetch(url, {\n ...options,\n headers: {\n ...defaultHeaders,\n ...options.headers\n }\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`MLflow API error: ${response.status} - ${errorText}`)\n }\n\n return await response.json()\n } catch (error) {\n console.error(`MLflow API request failed: ${endpoint}`, error)\n throw error\n }\n }\n\n // ========================================================================\n // Experiments API\n // ========================================================================\n\n /**\n * Create a new experiment\n */\n async createExperiment(params: {\n name: string\n artifact_location?: string\n tags?: Array<{ key: string; value: string }>\n }): Promise<{ experiment_id: string }> {\n return this.request('/api/2.0/mlflow/experiments/create', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Get experiment by ID\n */\n async getExperiment(experimentId: string): Promise<{\n experiment: {\n experiment_id: string\n name: string\n artifact_location: string\n lifecycle_stage: string\n tags?: Array<{ key: string; value: string }>\n }\n }> {\n return this.request(`/api/2.0/mlflow/experiments/get?experiment_id=${experimentId}`)\n }\n\n /**\n * Search experiments\n */\n async searchExperiments(params?: { max_results?: number; filter?: string; order_by?: string[] }): Promise<{\n experiments: Array<{\n experiment_id: string\n name: string\n artifact_location: string\n lifecycle_stage: string\n tags?: Array<{ key: string; value: string }>\n }>\n }> {\n const queryParams = new URLSearchParams()\n if (params?.max_results) queryParams.append('max_results', String(params.max_results))\n if (params?.filter) queryParams.append('filter', params.filter)\n\n return this.request(`/api/2.0/mlflow/experiments/search?${queryParams}`)\n }\n\n /**\n * Delete experiment\n */\n async deleteExperiment(experimentId: string): Promise<void> {\n return this.request('/api/2.0/mlflow/experiments/delete', {\n method: 'POST',\n body: JSON.stringify({ experiment_id: experimentId })\n })\n }\n\n // ========================================================================\n // Runs API\n // ========================================================================\n\n /**\n * Create a new run\n */\n async createRun(params: {\n experiment_id: string\n start_time?: number\n tags?: Array<{ key: string; value: string }>\n }): Promise<{\n run: {\n info: {\n run_id: string\n experiment_id: string\n status: string\n start_time: number\n artifact_uri: string\n }\n data: any\n }\n }> {\n return this.request('/api/2.0/mlflow/runs/create', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Get run by ID\n */\n async getRun(runId: string): Promise<{\n run: {\n info: {\n run_id: string\n experiment_id: string\n status: string\n start_time: number\n end_time?: number\n artifact_uri: string\n }\n data: {\n metrics: Array<{ key: string; value: number; timestamp: number; step: number }>\n params: Array<{ key: string; value: string }>\n tags: Array<{ key: string; value: string }>\n }\n }\n }> {\n return this.request(`/api/2.0/mlflow/runs/get?run_id=${runId}`)\n }\n\n /**\n * Search runs\n */\n async searchRuns(params: {\n experiment_ids: string[]\n filter?: string\n max_results?: number\n order_by?: string[]\n }): Promise<{\n runs: Array<{\n info: {\n run_id: string\n experiment_id: string\n status: string\n start_time: number\n end_time?: number\n artifact_uri: string\n }\n data: any\n }>\n }> {\n return this.request('/api/2.0/mlflow/runs/search', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Log metric for a run\n */\n async logMetric(params: {\n run_id: string\n key: string\n value: number\n timestamp?: number\n step?: number\n }): Promise<void> {\n return this.request('/api/2.0/mlflow/runs/log-metric', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Log parameter for a run\n */\n async logParam(params: { run_id: string; key: string; value: string }): Promise<void> {\n return this.request('/api/2.0/mlflow/runs/log-parameter', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Update run status\n */\n async updateRun(params: {\n run_id: string\n status?: 'RUNNING' | 'SCHEDULED' | 'FINISHED' | 'FAILED' | 'KILLED'\n end_time?: number\n }): Promise<void> {\n return this.request('/api/2.0/mlflow/runs/update', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n // ========================================================================\n // Model Registry API\n // ========================================================================\n\n /**\n * Create registered model\n */\n async createRegisteredModel(params: {\n name: string\n tags?: Array<{ key: string; value: string }>\n description?: string\n }): Promise<{\n registered_model: {\n name: string\n creation_timestamp: number\n last_updated_timestamp: number\n }\n }> {\n return this.request('/api/2.0/mlflow/registered-models/create', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Get registered model\n */\n async getRegisteredModel(name: string): Promise<{\n registered_model: {\n name: string\n creation_timestamp: number\n last_updated_timestamp: number\n description?: string\n tags?: Array<{ key: string; value: string }>\n latest_versions?: any[]\n }\n }> {\n return this.request(`/api/2.0/mlflow/registered-models/get?name=${encodeURIComponent(name)}`)\n }\n\n /**\n * Search registered models\n */\n async searchRegisteredModels(params?: { filter?: string; max_results?: number; order_by?: string[] }): Promise<{\n registered_models: Array<{\n name: string\n creation_timestamp: number\n last_updated_timestamp: number\n description?: string\n latest_versions?: any[]\n }>\n }> {\n const queryParams = new URLSearchParams()\n if (params?.filter) queryParams.append('filter', params.filter)\n if (params?.max_results) queryParams.append('max_results', String(params.max_results))\n\n return this.request(`/api/2.0/mlflow/registered-models/search?${queryParams}`)\n }\n\n /**\n * Create model version\n */\n async createModelVersion(params: {\n name: string\n source: string\n run_id?: string\n tags?: Array<{ key: string; value: string }>\n description?: string\n }): Promise<{\n model_version: {\n name: string\n version: string\n creation_timestamp: number\n last_updated_timestamp: number\n source: string\n status: string\n }\n }> {\n return this.request('/api/2.0/mlflow/model-versions/create', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Get model version\n */\n async getModelVersion(params: { name: string; version: string }): Promise<{\n model_version: {\n name: string\n version: string\n creation_timestamp: number\n current_stage: string\n source: string\n status: string\n run_id?: string\n }\n }> {\n return this.request(\n `/api/2.0/mlflow/model-versions/get?name=${encodeURIComponent(params.name)}&version=${params.version}`\n )\n }\n\n /**\n * Transition model version stage\n */\n async transitionModelVersionStage(params: {\n name: string\n version: string\n stage: 'None' | 'Staging' | 'Production' | 'Archived'\n archive_existing_versions?: boolean\n }): Promise<{\n model_version: {\n name: string\n version: string\n current_stage: string\n }\n }> {\n return this.request('/api/2.0/mlflow/model-versions/transition-stage', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Delete model version\n */\n async deleteModelVersion(params: { name: string; version: string }): Promise<void> {\n return this.request('/api/2.0/mlflow/model-versions/delete', {\n method: 'DELETE',\n body: JSON.stringify(params)\n })\n }\n}\n\n// Export singleton instance\nexport const mlflowApi = new MLflowApiClient()\n\n// Export class for testing or custom instances\nexport { MLflowApiClient }\n"]}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@things-factory/integration-mlflow",
3
+ "version": "9.1.19",
4
+ "main": "dist-server/index.js",
5
+ "browser": "dist-client/index.js",
6
+ "things-factory": true,
7
+ "author": "Things-Factory Team",
8
+ "description": "Integration module for MLflow - experiment tracking and model registry",
9
+ "license": "MIT",
10
+ "publishConfig": {
11
+ "access": "public",
12
+ "@things-factory:registry": "https://registry.npmjs.org"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/hatiolab/things-factory.git",
17
+ "directory": "packages/integration-mlflow"
18
+ },
19
+ "scripts": {
20
+ "build": "npm run build:server && npm run build:client",
21
+ "copy:files": "copyfiles -e \"./client/**/*.{ts,js,json}\" -u 1 \"./client/**/*\" dist-client",
22
+ "build:client": "npm run copy:files && npx tsc --p ./client/tsconfig.json",
23
+ "build:server": "npx tsc --p ./server/tsconfig.json",
24
+ "clean:client": "npx rimraf dist-client",
25
+ "clean:server": "npx rimraf dist-server",
26
+ "clean": "npm run clean:server && npm run clean:client"
27
+ },
28
+ "dependencies": {
29
+ "@operato/graphql": "^9.0.0",
30
+ "@operato/layout": "^9.0.0",
31
+ "@things-factory/auth-base": "^9.1.19",
32
+ "@things-factory/env": "^9.1.0",
33
+ "@things-factory/menu-base": "^9.1.19",
34
+ "@things-factory/shell": "^9.1.19"
35
+ },
36
+ "keywords": [
37
+ "things-factory",
38
+ "mlflow",
39
+ "integration",
40
+ "experiment-tracking",
41
+ "model-registry",
42
+ "machine-learning"
43
+ ],
44
+ "gitHead": "078438034dbe19915108e89ff24024f7044a85a9"
45
+ }
@@ -0,0 +1,5 @@
1
+ export * from './service/index.js'
2
+ export * from './types/mlflow-types.js'
3
+ export * from './utils/mlflow-api-client.js'
4
+
5
+ import './route.js'
@@ -0,0 +1,13 @@
1
+ /**
2
+ * MLflow Integration Routes
3
+ *
4
+ * Register additional routes if needed (beyond GraphQL)
5
+ */
6
+
7
+ // Example: Proxy to MLflow UI, webhooks, etc.
8
+ // import Koa from 'koa'
9
+ // import Router from 'koa-router'
10
+
11
+ // process.on('bootstrap-module-domain-private-route', (app: Koa, router: Router) => {
12
+ // // Add custom routes here
13
+ // })
@@ -0,0 +1,117 @@
1
+ import { Resolver, Query, Mutation, Arg, Ctx, Directive } from 'type-graphql'
2
+ import { mlflowApi } from '../../utils/mlflow-api-client'
3
+ import {
4
+ MLflowExperiment,
5
+ CreateExperimentInput
6
+ } from '../../types/mlflow-types'
7
+
8
+ @Resolver()
9
+ export class ExperimentService {
10
+ /**
11
+ * Get all experiments
12
+ */
13
+ @Query(returns => [MLflowExperiment], {
14
+ description: 'Get all MLflow experiments'
15
+ })
16
+ @Directive('@privilege(category: "mlflow", privilege: "query")')
17
+ async mlflowExperiments(@Ctx() context: ResolverContext): Promise<MLflowExperiment[]> {
18
+ try {
19
+ const response = await mlflowApi.searchExperiments()
20
+
21
+ return response.experiments.map(exp => ({
22
+ experimentId: exp.experiment_id,
23
+ name: exp.name,
24
+ artifactLocation: exp.artifact_location,
25
+ lifecycleStage: exp.lifecycle_stage,
26
+ tags: exp.tags ? JSON.stringify(exp.tags) : undefined
27
+ }))
28
+ } catch (error) {
29
+ console.error('Failed to fetch MLflow experiments:', error)
30
+ throw new Error(`Failed to fetch experiments: ${error.message}`)
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Get experiment by ID
36
+ */
37
+ @Query(returns => MLflowExperiment, {
38
+ description: 'Get MLflow experiment by ID',
39
+ nullable: true
40
+ })
41
+ @Directive('@privilege(category: "mlflow", privilege: "query")')
42
+ async mlflowExperiment(
43
+ @Arg('experimentId') experimentId: string,
44
+ @Ctx() context: ResolverContext
45
+ ): Promise<MLflowExperiment | null> {
46
+ try {
47
+ const response = await mlflowApi.getExperiment(experimentId)
48
+
49
+ return {
50
+ experimentId: response.experiment.experiment_id,
51
+ name: response.experiment.name,
52
+ artifactLocation: response.experiment.artifact_location,
53
+ lifecycleStage: response.experiment.lifecycle_stage,
54
+ tags: response.experiment.tags ? JSON.stringify(response.experiment.tags) : undefined
55
+ }
56
+ } catch (error) {
57
+ console.error(`Failed to fetch experiment ${experimentId}:`, error)
58
+ return null
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Create new experiment
64
+ */
65
+ @Mutation(returns => MLflowExperiment, {
66
+ description: 'Create new MLflow experiment'
67
+ })
68
+ @Directive('@privilege(category: "mlflow", privilege: "mutation")')
69
+ async createMLflowExperiment(
70
+ @Arg('input') input: CreateExperimentInput,
71
+ @Ctx() context: ResolverContext
72
+ ): Promise<MLflowExperiment> {
73
+ try {
74
+ const tags = input.tags ? JSON.parse(input.tags) : undefined
75
+
76
+ const response = await mlflowApi.createExperiment({
77
+ name: input.name,
78
+ artifact_location: input.artifactLocation,
79
+ tags
80
+ })
81
+
82
+ // Fetch created experiment
83
+ const experiment = await mlflowApi.getExperiment(response.experiment_id)
84
+
85
+ return {
86
+ experimentId: experiment.experiment.experiment_id,
87
+ name: experiment.experiment.name,
88
+ artifactLocation: experiment.experiment.artifact_location,
89
+ lifecycleStage: experiment.experiment.lifecycle_stage,
90
+ tags: experiment.experiment.tags ? JSON.stringify(experiment.experiment.tags) : undefined
91
+ }
92
+ } catch (error) {
93
+ console.error('Failed to create MLflow experiment:', error)
94
+ throw new Error(`Failed to create experiment: ${error.message}`)
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Delete experiment
100
+ */
101
+ @Mutation(returns => Boolean, {
102
+ description: 'Delete MLflow experiment'
103
+ })
104
+ @Directive('@privilege(category: "mlflow", privilege: "mutation")')
105
+ async deleteMLflowExperiment(
106
+ @Arg('experimentId') experimentId: string,
107
+ @Ctx() context: ResolverContext
108
+ ): Promise<boolean> {
109
+ try {
110
+ await mlflowApi.deleteExperiment(experimentId)
111
+ return true
112
+ } catch (error) {
113
+ console.error(`Failed to delete experiment ${experimentId}:`, error)
114
+ throw new Error(`Failed to delete experiment: ${error.message}`)
115
+ }
116
+ }
117
+ }