@objectstack/runtime 1.0.0 → 1.0.2
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/CHANGELOG.md +29 -0
- package/dist/http-dispatcher.d.ts +2 -2
- package/dist/http-dispatcher.js +27 -3
- package/dist/http-dispatcher.test.d.ts +1 -0
- package/dist/http-dispatcher.test.js +79 -0
- package/dist/rest-server.js +28 -0
- package/package.json +4 -4
- package/src/http-dispatcher.test.ts +107 -0
- package/src/http-dispatcher.ts +29 -4
- package/src/rest-server.ts +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @objectstack/runtime
|
|
2
2
|
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a0a6c85: Infrastructure and development tooling improvements
|
|
8
|
+
|
|
9
|
+
- Add changeset configuration for automated version management
|
|
10
|
+
- Add comprehensive GitHub Actions workflows (CI, CodeQL, linting, releases)
|
|
11
|
+
- Add development configuration files (.cursorrules, .github/prompts)
|
|
12
|
+
- Add documentation files (ARCHITECTURE.md, CONTRIBUTING.md, workflows docs)
|
|
13
|
+
- Update test script configuration in package.json
|
|
14
|
+
- Add @objectstack/cli to devDependencies for better development experience
|
|
15
|
+
|
|
16
|
+
- 109fc5b: Unified patch release to align all package versions.
|
|
17
|
+
- Updated dependencies [a0a6c85]
|
|
18
|
+
- Updated dependencies [109fc5b]
|
|
19
|
+
- @objectstack/spec@1.0.2
|
|
20
|
+
- @objectstack/core@1.0.2
|
|
21
|
+
- @objectstack/types@1.0.2
|
|
22
|
+
|
|
23
|
+
## 1.0.1
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Fix TypeScript error in http-dispatcher tests to resolve CI build failures.
|
|
28
|
+
- @objectstack/spec@1.0.1
|
|
29
|
+
- @objectstack/core@1.0.1
|
|
30
|
+
- @objectstack/types@1.0.1
|
|
31
|
+
|
|
3
32
|
## 1.0.0
|
|
4
33
|
|
|
5
34
|
### Major Changes
|
|
@@ -24,7 +24,7 @@ export declare class HttpDispatcher {
|
|
|
24
24
|
getDiscoveryInfo(prefix: string): {
|
|
25
25
|
name: string;
|
|
26
26
|
version: string;
|
|
27
|
-
environment: string;
|
|
27
|
+
environment: string | undefined;
|
|
28
28
|
routes: {
|
|
29
29
|
data: string;
|
|
30
30
|
metadata: string;
|
|
@@ -65,7 +65,7 @@ export declare class HttpDispatcher {
|
|
|
65
65
|
* Standard: /metadata/:type/:name
|
|
66
66
|
* Fallback for backward compat: /metadata (all objects), /metadata/:objectName (get object)
|
|
67
67
|
*/
|
|
68
|
-
handleMetadata(path: string, context: HttpProtocolContext): Promise<HttpDispatcherResult>;
|
|
68
|
+
handleMetadata(path: string, context: HttpProtocolContext, method?: string, body?: any): Promise<HttpDispatcherResult>;
|
|
69
69
|
/**
|
|
70
70
|
* Handles Data requests
|
|
71
71
|
* path: sub-path after /data/ (e.g. "contacts", "contacts/123", "contacts/query")
|
package/dist/http-dispatcher.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getEnv } from '@objectstack/core';
|
|
1
2
|
import { CoreServiceName } from '@objectstack/spec/system';
|
|
2
3
|
export class HttpDispatcher {
|
|
3
4
|
constructor(kernel) {
|
|
@@ -35,7 +36,7 @@ export class HttpDispatcher {
|
|
|
35
36
|
return {
|
|
36
37
|
name: 'ObjectOS',
|
|
37
38
|
version: '1.0.0',
|
|
38
|
-
environment:
|
|
39
|
+
environment: getEnv('NODE_ENV', 'development'),
|
|
39
40
|
routes: {
|
|
40
41
|
data: `${prefix}/data`,
|
|
41
42
|
metadata: `${prefix}/metadata`,
|
|
@@ -99,7 +100,7 @@ export class HttpDispatcher {
|
|
|
99
100
|
* Standard: /metadata/:type/:name
|
|
100
101
|
* Fallback for backward compat: /metadata (all objects), /metadata/:objectName (get object)
|
|
101
102
|
*/
|
|
102
|
-
async handleMetadata(path, context) {
|
|
103
|
+
async handleMetadata(path, context, method, body) {
|
|
103
104
|
const broker = this.ensureBroker();
|
|
104
105
|
const parts = path.replace(/^\/+/, '').split('/').filter(Boolean);
|
|
105
106
|
// GET /metadata/types
|
|
@@ -108,9 +109,32 @@ export class HttpDispatcher {
|
|
|
108
109
|
// For now we mock the types supported by core
|
|
109
110
|
return { handled: true, response: this.success({ types: ['objects', 'apps', 'plugins'] }) };
|
|
110
111
|
}
|
|
111
|
-
//
|
|
112
|
+
// /metadata/:type/:name
|
|
112
113
|
if (parts.length === 2) {
|
|
113
114
|
const [type, name] = parts;
|
|
115
|
+
// PUT /metadata/:type/:name (Save)
|
|
116
|
+
if (method === 'PUT' && body) {
|
|
117
|
+
// Try to get the protocol service directly
|
|
118
|
+
const protocol = this.kernel?.context?.getService ? this.kernel.context.getService('protocol') : null;
|
|
119
|
+
if (protocol && typeof protocol.saveMetaItem === 'function') {
|
|
120
|
+
try {
|
|
121
|
+
const result = await protocol.saveMetaItem({ type, name, item: body });
|
|
122
|
+
return { handled: true, response: this.success(result) };
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
return { handled: true, response: this.error(e.message, 400) };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Fallback to broker if protocol not available (legacy)
|
|
129
|
+
try {
|
|
130
|
+
const data = await broker.call('metadata.saveItem', { type, name, item: body }, { request: context.request });
|
|
131
|
+
return { handled: true, response: this.success(data) };
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
// If broker doesn't support it either
|
|
135
|
+
return { handled: true, response: this.error(e.message || 'Save not supported', 501) };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
114
138
|
try {
|
|
115
139
|
// Try specific calls based on type
|
|
116
140
|
if (type === 'objects') {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { HttpDispatcher } from './http-dispatcher.js';
|
|
3
|
+
describe('HttpDispatcher', () => {
|
|
4
|
+
let kernel;
|
|
5
|
+
let dispatcher;
|
|
6
|
+
let mockProtocol;
|
|
7
|
+
let mockBroker;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// Mock Kernel
|
|
10
|
+
mockProtocol = {
|
|
11
|
+
saveMetaItem: vi.fn().mockResolvedValue({ success: true, message: 'Saved' }),
|
|
12
|
+
getMetaItem: vi.fn().mockResolvedValue({ success: true, item: { foo: 'bar' } })
|
|
13
|
+
};
|
|
14
|
+
mockBroker = {
|
|
15
|
+
call: vi.fn(),
|
|
16
|
+
};
|
|
17
|
+
kernel = {
|
|
18
|
+
broker: mockBroker,
|
|
19
|
+
context: {
|
|
20
|
+
getService: (name) => {
|
|
21
|
+
if (name === 'protocol')
|
|
22
|
+
return mockProtocol;
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
dispatcher = new HttpDispatcher(kernel);
|
|
28
|
+
});
|
|
29
|
+
describe('handleMetadata', () => {
|
|
30
|
+
it('should handle PUT /metadata/:type/:name by calling protocol.saveMetaItem', async () => {
|
|
31
|
+
const context = { request: {} };
|
|
32
|
+
const body = { label: 'New Label' };
|
|
33
|
+
const path = '/objects/my_obj';
|
|
34
|
+
const result = await dispatcher.handleMetadata(path, context, 'PUT', body);
|
|
35
|
+
expect(result.handled).toBe(true);
|
|
36
|
+
expect(result.response?.status).toBe(200);
|
|
37
|
+
expect(mockProtocol.saveMetaItem).toHaveBeenCalledWith({
|
|
38
|
+
type: 'objects',
|
|
39
|
+
name: 'my_obj',
|
|
40
|
+
item: body
|
|
41
|
+
});
|
|
42
|
+
expect(result.response?.body).toEqual({
|
|
43
|
+
success: true,
|
|
44
|
+
data: { success: true, message: 'Saved' },
|
|
45
|
+
meta: undefined
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
it('should fallback to broker call if protocol is missing saveMetaItem', async () => {
|
|
49
|
+
// Mock protocol without saveMetaItem
|
|
50
|
+
kernel.context.getService = () => ({});
|
|
51
|
+
// Mock broker success
|
|
52
|
+
mockBroker.call.mockResolvedValue({ success: true, fromBroker: true });
|
|
53
|
+
const context = { request: {} };
|
|
54
|
+
const body = { label: 'Fallback' };
|
|
55
|
+
const path = '/objects/my_obj';
|
|
56
|
+
const result = await dispatcher.handleMetadata(path, context, 'PUT', body);
|
|
57
|
+
expect(result.handled).toBe(true);
|
|
58
|
+
expect(mockBroker.call).toHaveBeenCalledWith('metadata.saveItem', { type: 'objects', name: 'my_obj', item: body }, { request: context.request });
|
|
59
|
+
expect(result.response?.body?.data).toEqual({ success: true, fromBroker: true });
|
|
60
|
+
});
|
|
61
|
+
it('should return error if save fails', async () => {
|
|
62
|
+
mockProtocol.saveMetaItem.mockRejectedValue(new Error('Save failed'));
|
|
63
|
+
const context = { request: {} };
|
|
64
|
+
const body = {};
|
|
65
|
+
const path = '/objects/bad_obj';
|
|
66
|
+
const result = await dispatcher.handleMetadata(path, context, 'PUT', body);
|
|
67
|
+
expect(result.handled).toBe(true);
|
|
68
|
+
expect(result.response?.status).toBe(400);
|
|
69
|
+
expect(result.response?.body?.error?.message).toBe('Save failed');
|
|
70
|
+
});
|
|
71
|
+
it('should handle READ operations as before', async () => {
|
|
72
|
+
mockBroker.call.mockResolvedValue({ name: 'my_obj' });
|
|
73
|
+
const context = { request: {} };
|
|
74
|
+
const result = await dispatcher.handleMetadata('/objects/my_obj', context, 'GET');
|
|
75
|
+
expect(result.handled).toBe(true);
|
|
76
|
+
expect(mockBroker.call).toHaveBeenCalledWith('metadata.getObject', { objectName: 'my_obj' }, { request: context.request });
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
package/dist/rest-server.js
CHANGED
|
@@ -250,6 +250,34 @@ export class RestServer {
|
|
|
250
250
|
},
|
|
251
251
|
});
|
|
252
252
|
}
|
|
253
|
+
// PUT /meta/:type/:name - Save metadata item
|
|
254
|
+
// We always register this route, but return 501 if protocol doesn't support it
|
|
255
|
+
// This makes it discoverable even if not implemented
|
|
256
|
+
this.routeManager.register({
|
|
257
|
+
method: 'PUT',
|
|
258
|
+
path: `${metaPath}/:type/:name`,
|
|
259
|
+
handler: async (req, res) => {
|
|
260
|
+
try {
|
|
261
|
+
if (!this.protocol.saveMetaItem) {
|
|
262
|
+
res.status(501).json({ error: 'Save operation not supported by protocol implementation' });
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const result = await this.protocol.saveMetaItem({
|
|
266
|
+
type: req.params.type,
|
|
267
|
+
name: req.params.name,
|
|
268
|
+
item: req.body
|
|
269
|
+
});
|
|
270
|
+
res.json(result);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
res.status(400).json({ error: error.message });
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
metadata: {
|
|
277
|
+
summary: 'Save specific metadata item',
|
|
278
|
+
tags: ['metadata'],
|
|
279
|
+
},
|
|
280
|
+
});
|
|
253
281
|
}
|
|
254
282
|
/**
|
|
255
283
|
* Register CRUD endpoints for data operations
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/runtime",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "ObjectStack Core Runtime & Query Engine",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@objectstack/core": "1.0.
|
|
11
|
-
"@objectstack/spec": "1.0.
|
|
12
|
-
"@objectstack/types": "1.0.
|
|
10
|
+
"@objectstack/core": "1.0.2",
|
|
11
|
+
"@objectstack/spec": "1.0.2",
|
|
12
|
+
"@objectstack/types": "1.0.2"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"typescript": "^5.0.0",
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import { HttpDispatcher } from './http-dispatcher.js';
|
|
4
|
+
import { ObjectKernel } from '@objectstack/core';
|
|
5
|
+
|
|
6
|
+
describe('HttpDispatcher', () => {
|
|
7
|
+
let kernel: ObjectKernel;
|
|
8
|
+
let dispatcher: HttpDispatcher;
|
|
9
|
+
let mockProtocol: any;
|
|
10
|
+
let mockBroker: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Mock Kernel
|
|
14
|
+
mockProtocol = {
|
|
15
|
+
saveMetaItem: vi.fn().mockResolvedValue({ success: true, message: 'Saved' }),
|
|
16
|
+
getMetaItem: vi.fn().mockResolvedValue({ success: true, item: { foo: 'bar' } })
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
mockBroker = {
|
|
20
|
+
call: vi.fn(),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
kernel = {
|
|
24
|
+
broker: mockBroker,
|
|
25
|
+
context: {
|
|
26
|
+
getService: (name: string) => {
|
|
27
|
+
if (name === 'protocol') return mockProtocol;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} as any;
|
|
32
|
+
|
|
33
|
+
dispatcher = new HttpDispatcher(kernel);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('handleMetadata', () => {
|
|
37
|
+
it('should handle PUT /metadata/:type/:name by calling protocol.saveMetaItem', async () => {
|
|
38
|
+
const context = { request: {} };
|
|
39
|
+
const body = { label: 'New Label' };
|
|
40
|
+
const path = '/objects/my_obj';
|
|
41
|
+
|
|
42
|
+
const result = await dispatcher.handleMetadata(path, context, 'PUT', body);
|
|
43
|
+
|
|
44
|
+
expect(result.handled).toBe(true);
|
|
45
|
+
expect(result.response?.status).toBe(200);
|
|
46
|
+
expect(mockProtocol.saveMetaItem).toHaveBeenCalledWith({
|
|
47
|
+
type: 'objects',
|
|
48
|
+
name: 'my_obj',
|
|
49
|
+
item: body
|
|
50
|
+
});
|
|
51
|
+
expect(result.response?.body).toEqual({
|
|
52
|
+
success: true,
|
|
53
|
+
data: { success: true, message: 'Saved' },
|
|
54
|
+
meta: undefined
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should fallback to broker call if protocol is missing saveMetaItem', async () => {
|
|
59
|
+
// Mock protocol without saveMetaItem
|
|
60
|
+
(kernel as any).context.getService = () => ({});
|
|
61
|
+
// Mock broker success
|
|
62
|
+
mockBroker.call.mockResolvedValue({ success: true, fromBroker: true });
|
|
63
|
+
|
|
64
|
+
const context = { request: {} };
|
|
65
|
+
const body = { label: 'Fallback' };
|
|
66
|
+
const path = '/objects/my_obj';
|
|
67
|
+
|
|
68
|
+
const result = await dispatcher.handleMetadata(path, context, 'PUT', body);
|
|
69
|
+
|
|
70
|
+
expect(result.handled).toBe(true);
|
|
71
|
+
expect(mockBroker.call).toHaveBeenCalledWith(
|
|
72
|
+
'metadata.saveItem',
|
|
73
|
+
{ type: 'objects', name: 'my_obj', item: body },
|
|
74
|
+
{ request: context.request }
|
|
75
|
+
);
|
|
76
|
+
expect(result.response?.body?.data).toEqual({ success: true, fromBroker: true });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return error if save fails', async () => {
|
|
80
|
+
mockProtocol.saveMetaItem.mockRejectedValue(new Error('Save failed'));
|
|
81
|
+
|
|
82
|
+
const context = { request: {} };
|
|
83
|
+
const body = {};
|
|
84
|
+
const path = '/objects/bad_obj';
|
|
85
|
+
|
|
86
|
+
const result = await dispatcher.handleMetadata(path, context, 'PUT', body);
|
|
87
|
+
|
|
88
|
+
expect(result.handled).toBe(true);
|
|
89
|
+
expect(result.response?.status).toBe(400);
|
|
90
|
+
expect(result.response?.body?.error?.message).toBe('Save failed');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should handle READ operations as before', async () => {
|
|
94
|
+
mockBroker.call.mockResolvedValue({ name: 'my_obj' });
|
|
95
|
+
|
|
96
|
+
const context = { request: {} };
|
|
97
|
+
const result = await dispatcher.handleMetadata('/objects/my_obj', context, 'GET');
|
|
98
|
+
|
|
99
|
+
expect(result.handled).toBe(true);
|
|
100
|
+
expect(mockBroker.call).toHaveBeenCalledWith(
|
|
101
|
+
'metadata.getObject',
|
|
102
|
+
{ objectName: 'my_obj' },
|
|
103
|
+
{ request: context.request }
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
package/src/http-dispatcher.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ObjectKernel } from '@objectstack/core';
|
|
1
|
+
import { ObjectKernel, getEnv } from '@objectstack/core';
|
|
2
2
|
import { CoreServiceName } from '@objectstack/spec/system';
|
|
3
3
|
|
|
4
4
|
export interface HttpProtocolContext {
|
|
@@ -60,7 +60,7 @@ export class HttpDispatcher {
|
|
|
60
60
|
return {
|
|
61
61
|
name: 'ObjectOS',
|
|
62
62
|
version: '1.0.0',
|
|
63
|
-
environment:
|
|
63
|
+
environment: getEnv('NODE_ENV', 'development'),
|
|
64
64
|
routes: {
|
|
65
65
|
data: `${prefix}/data`,
|
|
66
66
|
metadata: `${prefix}/metadata`,
|
|
@@ -131,7 +131,7 @@ export class HttpDispatcher {
|
|
|
131
131
|
* Standard: /metadata/:type/:name
|
|
132
132
|
* Fallback for backward compat: /metadata (all objects), /metadata/:objectName (get object)
|
|
133
133
|
*/
|
|
134
|
-
async handleMetadata(path: string, context: HttpProtocolContext): Promise<HttpDispatcherResult> {
|
|
134
|
+
async handleMetadata(path: string, context: HttpProtocolContext, method?: string, body?: any): Promise<HttpDispatcherResult> {
|
|
135
135
|
const broker = this.ensureBroker();
|
|
136
136
|
const parts = path.replace(/^\/+/, '').split('/').filter(Boolean);
|
|
137
137
|
|
|
@@ -142,9 +142,34 @@ export class HttpDispatcher {
|
|
|
142
142
|
return { handled: true, response: this.success({ types: ['objects', 'apps', 'plugins'] }) };
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
//
|
|
145
|
+
// /metadata/:type/:name
|
|
146
146
|
if (parts.length === 2) {
|
|
147
147
|
const [type, name] = parts;
|
|
148
|
+
|
|
149
|
+
// PUT /metadata/:type/:name (Save)
|
|
150
|
+
if (method === 'PUT' && body) {
|
|
151
|
+
// Try to get the protocol service directly
|
|
152
|
+
const protocol = this.kernel?.context?.getService ? this.kernel.context.getService('protocol') : null;
|
|
153
|
+
|
|
154
|
+
if (protocol && typeof protocol.saveMetaItem === 'function') {
|
|
155
|
+
try {
|
|
156
|
+
const result = await protocol.saveMetaItem({ type, name, item: body });
|
|
157
|
+
return { handled: true, response: this.success(result) };
|
|
158
|
+
} catch (e: any) {
|
|
159
|
+
return { handled: true, response: this.error(e.message, 400) };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Fallback to broker if protocol not available (legacy)
|
|
164
|
+
try {
|
|
165
|
+
const data = await broker.call('metadata.saveItem', { type, name, item: body }, { request: context.request });
|
|
166
|
+
return { handled: true, response: this.success(data) };
|
|
167
|
+
} catch (e: any) {
|
|
168
|
+
// If broker doesn't support it either
|
|
169
|
+
return { handled: true, response: this.error(e.message || 'Save not supported', 501) };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
148
173
|
try {
|
|
149
174
|
// Try specific calls based on type
|
|
150
175
|
if (type === 'objects') {
|
package/src/rest-server.ts
CHANGED
|
@@ -333,6 +333,35 @@ export class RestServer {
|
|
|
333
333
|
},
|
|
334
334
|
});
|
|
335
335
|
}
|
|
336
|
+
|
|
337
|
+
// PUT /meta/:type/:name - Save metadata item
|
|
338
|
+
// We always register this route, but return 501 if protocol doesn't support it
|
|
339
|
+
// This makes it discoverable even if not implemented
|
|
340
|
+
this.routeManager.register({
|
|
341
|
+
method: 'PUT',
|
|
342
|
+
path: `${metaPath}/:type/:name`,
|
|
343
|
+
handler: async (req: any, res: any) => {
|
|
344
|
+
try {
|
|
345
|
+
if (!this.protocol.saveMetaItem) {
|
|
346
|
+
res.status(501).json({ error: 'Save operation not supported by protocol implementation' });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const result = await this.protocol.saveMetaItem({
|
|
351
|
+
type: req.params.type,
|
|
352
|
+
name: req.params.name,
|
|
353
|
+
item: req.body
|
|
354
|
+
});
|
|
355
|
+
res.json(result);
|
|
356
|
+
} catch (error: any) {
|
|
357
|
+
res.status(400).json({ error: error.message });
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
metadata: {
|
|
361
|
+
summary: 'Save specific metadata item',
|
|
362
|
+
tags: ['metadata'],
|
|
363
|
+
},
|
|
364
|
+
});
|
|
336
365
|
}
|
|
337
366
|
|
|
338
367
|
/**
|