@geode/opengeodeweb-front 10.35.0 → 10.36.0-rc.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/.build/opengeodeweb_front_schemas.js +200 -21
- package/.build/server/utils/typed_handler.js +63 -0
- package/.build/shared/utils/validate_schema.js +2 -1
- package/app/components/CrsSelector.vue +5 -5
- package/app/components/ExtensionSelector.vue +6 -12
- package/app/components/FileSelector.vue +2 -4
- package/app/components/MissingFilesSelector.vue +1 -1
- package/app/components/ObjectSelector.vue +1 -1
- package/app/components/ThresholdFilter.vue +1 -1
- package/app/components/Viewer/Generic/Model/BlocksOptions.vue +1 -1
- package/app/components/Viewer/Generic/Model/CornersOptions.vue +1 -1
- package/app/components/Viewer/Generic/Model/LinesOptions.vue +1 -1
- package/app/components/Viewer/Generic/Model/SurfacesOptions.vue +1 -1
- package/app/components/Viewer/Options/ColoringTypeSelector.vue +1 -1
- package/app/components/Viewer/Options/TextureItem.vue +5 -7
- package/app/composables/global_attribute_style.ts +1 -1
- package/app/composables/project_manager.ts +1 -1
- package/app/stores/app.ts +19 -17
- package/app/stores/back.ts +27 -21
- package/app/stores/cloud.ts +1 -1
- package/app/stores/infra.ts +1 -1
- package/app/stores/viewer.ts +4 -6
- package/app/utils/extension.ts +4 -4
- package/app/utils/import_workflow.ts +1 -1
- package/internal/stores/hybrid_viewer/base64.ts +6 -0
- package/internal/stores/hybrid_viewer/scene.ts +5 -1
- package/internal/utils/api_fetch.ts +9 -8
- package/opengeodeweb_front_schemas.json +252 -19
- package/opengeodeweb_front_typed_schemas.d.ts +225 -0
- package/opengeodeweb_front_typed_schemas.js +4 -0
- package/package.json +7 -5
- package/server/api/cloud/extensions/run.post.ts +9 -21
- package/server/api/cloud/extensions/schemas/run.json +42 -3
- package/server/api/local/app/kill.post.ts +4 -2
- package/server/api/local/app/project_folder_path.post.ts +6 -22
- package/server/api/local/app/run_back.post.ts +8 -23
- package/server/api/local/app/run_viewer.post.ts +8 -23
- package/server/api/local/app/schemas/kill.json +7 -1
- package/server/api/local/app/schemas/project_folder_path.json +19 -2
- package/server/api/local/app/schemas/run_back.json +31 -4
- package/server/api/local/app/schemas/run_viewer.json +31 -4
- package/server/api/local/extensions/kill.post.ts +8 -23
- package/server/api/local/extensions/run.post.ts +8 -22
- package/server/api/local/extensions/schemas/kill.json +20 -4
- package/server/api/local/extensions/schemas/run.json +42 -3
- package/server/api/local/extensions/schemas/upload.json +11 -1
- package/server/api/local/extensions/upload.put.ts +37 -29
- package/server/api/microservice/app/get_is_app_ready.get.ts +7 -13
- package/server/api/microservice/app/schemas/get_is_app_ready.json +14 -1
- package/server/api/microservice/app/schemas/set_app_base_url.json +17 -2
- package/server/api/microservice/app/schemas/set_back_base_url.json +17 -2
- package/server/api/microservice/app/schemas/set_is_app_ready.json +17 -2
- package/server/api/microservice/app/schemas/set_viewer_base_url.json +17 -2
- package/server/api/microservice/app/set_app_base_url.post.ts +8 -17
- package/server/api/microservice/app/set_back_base_url.post.ts +8 -16
- package/server/api/microservice/app/set_is_app_ready.post.ts +8 -17
- package/server/api/microservice/app/set_viewer_base_url.post.ts +8 -16
- package/server/api/microservice/extensions/download.post.ts +8 -21
- package/server/api/microservice/extensions/schemas/download.json +21 -4
- package/server/utils/typed_handler.ts +114 -0
- package/shared/utils/types.ts +18 -3
- package/shared/utils/validate_schema.ts +1 -1
|
@@ -4,5 +4,18 @@
|
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {},
|
|
6
6
|
"required": [],
|
|
7
|
-
"additionalProperties": false
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"response": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"statusCode": {
|
|
12
|
+
"type": "integer"
|
|
13
|
+
},
|
|
14
|
+
"isReady": {
|
|
15
|
+
"type": "boolean"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"required": ["statusCode", "isReady"],
|
|
19
|
+
"additionalProperties": false
|
|
20
|
+
}
|
|
8
21
|
}
|
|
@@ -3,8 +3,23 @@
|
|
|
3
3
|
"methods": ["POST"],
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
6
|
-
"baseUrl": {
|
|
6
|
+
"baseUrl": {
|
|
7
|
+
"type": "string"
|
|
8
|
+
}
|
|
7
9
|
},
|
|
8
10
|
"required": ["baseUrl"],
|
|
9
|
-
"additionalProperties": false
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"response": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"statusCode": {
|
|
16
|
+
"type": "integer"
|
|
17
|
+
},
|
|
18
|
+
"baseUrl": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": ["statusCode", "baseUrl"],
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
}
|
|
10
25
|
}
|
|
@@ -3,8 +3,23 @@
|
|
|
3
3
|
"methods": ["POST"],
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
6
|
-
"baseUrl": {
|
|
6
|
+
"baseUrl": {
|
|
7
|
+
"type": "string"
|
|
8
|
+
}
|
|
7
9
|
},
|
|
8
10
|
"required": ["baseUrl"],
|
|
9
|
-
"additionalProperties": false
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"response": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"statusCode": {
|
|
16
|
+
"type": "integer"
|
|
17
|
+
},
|
|
18
|
+
"baseUrl": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": ["statusCode", "baseUrl"],
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
}
|
|
10
25
|
}
|
|
@@ -3,8 +3,23 @@
|
|
|
3
3
|
"methods": ["POST"],
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
6
|
-
"isReady": {
|
|
6
|
+
"isReady": {
|
|
7
|
+
"type": "boolean"
|
|
8
|
+
}
|
|
7
9
|
},
|
|
8
10
|
"required": ["isReady"],
|
|
9
|
-
"additionalProperties": false
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"response": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"statusCode": {
|
|
16
|
+
"type": "integer"
|
|
17
|
+
},
|
|
18
|
+
"isReady": {
|
|
19
|
+
"type": "boolean"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": ["statusCode", "isReady"],
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
}
|
|
10
25
|
}
|
|
@@ -3,8 +3,23 @@
|
|
|
3
3
|
"methods": ["POST"],
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
6
|
-
"baseUrl": {
|
|
6
|
+
"baseUrl": {
|
|
7
|
+
"type": "string"
|
|
8
|
+
}
|
|
7
9
|
},
|
|
8
10
|
"required": ["baseUrl"],
|
|
9
|
-
"additionalProperties": false
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"response": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"statusCode": {
|
|
16
|
+
"type": "integer"
|
|
17
|
+
},
|
|
18
|
+
"baseUrl": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": ["statusCode", "baseUrl"],
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
}
|
|
10
25
|
}
|
|
@@ -1,30 +1,21 @@
|
|
|
1
1
|
// Third party imports
|
|
2
|
-
import { type H3Event, createError, defineEventHandler, readBody } from "h3";
|
|
3
2
|
import { consola } from "consola";
|
|
3
|
+
import { createError } from "h3";
|
|
4
4
|
|
|
5
5
|
// Local imports
|
|
6
6
|
import { setAppBaseUrl } from "@geode/opengeodeweb-front/server/utils/server_config.ts";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
8
|
+
import { defineTypedEventHandler } from "@geode/opengeodeweb-front/server/utils/typed_handler.ts";
|
|
9
|
+
import schemas from "@geode/opengeodeweb-front/opengeodeweb_front_typed_schemas.js";
|
|
11
10
|
|
|
12
|
-
export default
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export default defineTypedEventHandler(
|
|
12
|
+
schemas.api.microservice.app.set_app_base_url,
|
|
13
|
+
({ baseUrl }) => {
|
|
15
14
|
if (!baseUrl) {
|
|
16
15
|
throw createError({ statusCode: 400, statusMessage: "baseUrl is required" });
|
|
17
16
|
}
|
|
18
|
-
|
|
19
17
|
setAppBaseUrl(baseUrl);
|
|
20
18
|
consola.info(`Updated APP_BASE_URL to ${baseUrl}`);
|
|
21
|
-
|
|
22
19
|
return { statusCode: 200, baseUrl };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
throw createError({
|
|
26
|
-
statusCode: 500,
|
|
27
|
-
statusMessage: error instanceof Error ? error.message : String(error),
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
});
|
|
20
|
+
},
|
|
21
|
+
);
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
// Third party imports
|
|
2
|
-
import {
|
|
3
|
-
import { consola } from "consola";
|
|
2
|
+
import { createError } from "h3";
|
|
4
3
|
|
|
5
4
|
// Local imports
|
|
6
5
|
import { setBackBaseUrl } from "@geode/opengeodeweb-front/server/utils/server_config.ts";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
7
|
+
import { defineTypedEventHandler } from "@geode/opengeodeweb-front/server/utils/typed_handler.ts";
|
|
8
|
+
import schemas from "@geode/opengeodeweb-front/opengeodeweb_front_typed_schemas.js";
|
|
11
9
|
|
|
12
|
-
export default
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
export default defineTypedEventHandler(
|
|
11
|
+
schemas.api.microservice.app.set_back_base_url,
|
|
12
|
+
({ baseUrl }) => {
|
|
15
13
|
if (!baseUrl) {
|
|
16
14
|
throw createError({ statusCode: 400, statusMessage: "baseUrl is required" });
|
|
17
15
|
}
|
|
18
16
|
setBackBaseUrl(baseUrl);
|
|
19
17
|
return { statusCode: 200, baseUrl };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
throw createError({
|
|
23
|
-
statusCode: 500,
|
|
24
|
-
statusMessage: error instanceof Error ? error.message : String(error),
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
});
|
|
18
|
+
},
|
|
19
|
+
);
|
|
@@ -1,30 +1,21 @@
|
|
|
1
1
|
// Third party imports
|
|
2
|
-
import { type H3Event, createError, defineEventHandler, readBody } from "h3";
|
|
3
2
|
import { consola } from "consola";
|
|
3
|
+
import { createError } from "h3";
|
|
4
4
|
|
|
5
5
|
// Local imports
|
|
6
6
|
import { setIsAppReady } from "@geode/opengeodeweb-front/server/utils/server_config.ts";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
8
|
+
import { defineTypedEventHandler } from "@geode/opengeodeweb-front/server/utils/typed_handler.ts";
|
|
9
|
+
import schemas from "@geode/opengeodeweb-front/opengeodeweb_front_typed_schemas.js";
|
|
11
10
|
|
|
12
|
-
export default
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export default defineTypedEventHandler(
|
|
12
|
+
schemas.api.microservice.app.set_is_app_ready,
|
|
13
|
+
({ isReady }) => {
|
|
15
14
|
if (!isReady) {
|
|
16
15
|
throw createError({ statusCode: 400, statusMessage: "isReady is required" });
|
|
17
16
|
}
|
|
18
|
-
|
|
19
17
|
setIsAppReady(isReady);
|
|
20
18
|
consola.info(`Updated IS_APP_READY to ${isReady}`);
|
|
21
|
-
|
|
22
19
|
return { statusCode: 200, isReady };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
throw createError({
|
|
26
|
-
statusCode: 500,
|
|
27
|
-
statusMessage: error instanceof Error ? error.message : String(error),
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
});
|
|
20
|
+
},
|
|
21
|
+
);
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
// Third party imports
|
|
2
|
-
import {
|
|
3
|
-
import { consola } from "consola";
|
|
2
|
+
import { createError } from "h3";
|
|
4
3
|
|
|
5
4
|
// Local imports
|
|
6
5
|
import { setViewerBaseUrl } from "@geode/opengeodeweb-front/server/utils/server_config.ts";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
7
|
+
import { defineTypedEventHandler } from "@geode/opengeodeweb-front/server/utils/typed_handler.ts";
|
|
8
|
+
import schemas from "@geode/opengeodeweb-front/opengeodeweb_front_typed_schemas.js";
|
|
11
9
|
|
|
12
|
-
export default
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
export default defineTypedEventHandler(
|
|
11
|
+
schemas.api.microservice.app.set_viewer_base_url,
|
|
12
|
+
({ baseUrl }) => {
|
|
15
13
|
if (!baseUrl) {
|
|
16
14
|
throw createError({ statusCode: 400, statusMessage: "baseUrl is required" });
|
|
17
15
|
}
|
|
18
16
|
setViewerBaseUrl(baseUrl);
|
|
19
17
|
return { statusCode: 200, baseUrl };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
throw createError({
|
|
23
|
-
statusCode: 500,
|
|
24
|
-
statusMessage: error instanceof Error ? error.message : String(error),
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
});
|
|
18
|
+
},
|
|
19
|
+
);
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { promises as fs } from "node:fs";
|
|
3
3
|
|
|
4
4
|
// Third party imports
|
|
5
|
-
import { type H3Event, createError, defineEventHandler, readBody } from "h3";
|
|
6
5
|
import { consola } from "consola";
|
|
7
6
|
|
|
8
7
|
// Local imports
|
|
@@ -11,30 +10,18 @@ import {
|
|
|
11
10
|
targetExtensionFilePath,
|
|
12
11
|
} from "@geode/opengeodeweb-front/server/utils/app_config.ts";
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
url: string;
|
|
17
|
-
extensionFileName: string;
|
|
18
|
-
}
|
|
13
|
+
import { defineTypedEventHandler } from "@geode/opengeodeweb-front/server/utils/typed_handler.ts";
|
|
14
|
+
import schemas from "@geode/opengeodeweb-front/opengeodeweb_front_typed_schemas.js";
|
|
19
15
|
|
|
20
|
-
export default
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const { projectName, url, extensionFileName } = body;
|
|
16
|
+
export default defineTypedEventHandler(
|
|
17
|
+
schemas.api.microservice.extensions.download,
|
|
18
|
+
async ({ projectName, url, extensionFileName }) => {
|
|
24
19
|
consola.debug({ projectName, url, extensionFileName });
|
|
25
20
|
const response: Response = await fetch(url);
|
|
26
21
|
const fileBuffer = await response.arrayBuffer();
|
|
27
22
|
const filePath = targetExtensionFilePath(projectName, extensionFileName);
|
|
28
23
|
await fs.writeFile(filePath, Buffer.from(fileBuffer));
|
|
29
24
|
await registerExtensionFile(projectName, filePath);
|
|
30
|
-
return {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
} catch (error) {
|
|
34
|
-
consola.error("Error downloading extension:", error);
|
|
35
|
-
throw createError({
|
|
36
|
-
statusCode: 500,
|
|
37
|
-
statusMessage: error instanceof Error ? error.message : String(error),
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
});
|
|
25
|
+
return { statusCode: 200 };
|
|
26
|
+
},
|
|
27
|
+
);
|
|
@@ -3,9 +3,26 @@
|
|
|
3
3
|
"methods": ["POST"],
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
6
|
-
"
|
|
7
|
-
|
|
6
|
+
"projectName": {
|
|
7
|
+
"type": "string"
|
|
8
|
+
},
|
|
9
|
+
"url": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
"extensionFileName": {
|
|
13
|
+
"type": "string"
|
|
14
|
+
}
|
|
8
15
|
},
|
|
9
|
-
"required": ["
|
|
10
|
-
"additionalProperties": false
|
|
16
|
+
"required": ["projectName", "url", "extensionFileName"],
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"response": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"statusCode": {
|
|
22
|
+
"type": "integer"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": ["statusCode"],
|
|
26
|
+
"additionalProperties": false
|
|
27
|
+
}
|
|
11
28
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Third party imports
|
|
2
|
+
import {
|
|
3
|
+
type EventHandler,
|
|
4
|
+
type H3Event,
|
|
5
|
+
createError,
|
|
6
|
+
defineEventHandler,
|
|
7
|
+
getQuery,
|
|
8
|
+
isError,
|
|
9
|
+
readBody,
|
|
10
|
+
setResponseStatus,
|
|
11
|
+
} from "h3";
|
|
12
|
+
import Ajv from "ajv";
|
|
13
|
+
import { consola } from "consola";
|
|
14
|
+
|
|
15
|
+
// Local imports
|
|
16
|
+
import type {
|
|
17
|
+
JsonRpcSchema,
|
|
18
|
+
ParamsOf,
|
|
19
|
+
ResponseOf,
|
|
20
|
+
} from "@geode/opengeodeweb-front/shared/utils/types.ts";
|
|
21
|
+
import type { ErrorResponse } from "@geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js";
|
|
22
|
+
import { validateSchema } from "@geode/opengeodeweb-front/shared/utils/validate_schema.ts";
|
|
23
|
+
|
|
24
|
+
const BAD_REQUEST = 400;
|
|
25
|
+
const INTERNAL_SERVER_ERROR = 500;
|
|
26
|
+
|
|
27
|
+
type RouteSchema = JsonRpcSchema & { readonly response?: object };
|
|
28
|
+
|
|
29
|
+
// Same body as the Flask microservices' errors, so the client reads every error the same way
|
|
30
|
+
function toErrorResponse(error: unknown): ErrorResponse {
|
|
31
|
+
if (isError(error)) {
|
|
32
|
+
return {
|
|
33
|
+
code: error.statusCode,
|
|
34
|
+
name: error.statusMessage ?? "Error",
|
|
35
|
+
description: error.message,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
code: INTERNAL_SERVER_ERROR,
|
|
40
|
+
name: "Internal Server Error",
|
|
41
|
+
description: error instanceof Error ? error.message : String(error),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sendError(event: H3Event, schema: RouteSchema, error: unknown): ErrorResponse {
|
|
46
|
+
consola.error(`[${schema.$id}]`, error);
|
|
47
|
+
const errorResponse = toErrorResponse(error);
|
|
48
|
+
setResponseStatus(event, errorResponse.code, errorResponse.name);
|
|
49
|
+
return errorResponse;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function readParams(event: H3Event): Promise<unknown> {
|
|
53
|
+
if (event.method === "GET" || event.method === "HEAD") {
|
|
54
|
+
return getQuery(event);
|
|
55
|
+
}
|
|
56
|
+
const body: unknown = await readBody(event);
|
|
57
|
+
return body ?? {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function checkResponse(schema: RouteSchema, payload: unknown): void {
|
|
61
|
+
if (process.env.NODE_ENV === "production" || schema.response === undefined) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const ajv = new Ajv();
|
|
65
|
+
if (!ajv.validate(schema.response, payload)) {
|
|
66
|
+
throw new Error(`${schema.$id} response does not match its schema: ${ajv.errorsText()}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Register a JSON route whose handler takes its typed params and returns its typed success response. Errors must be thrown (createError or any exception), they are sent as an ErrorResponse.
|
|
71
|
+
function defineTypedEventHandler<Schema extends RouteSchema>(
|
|
72
|
+
schema: Schema,
|
|
73
|
+
handler: (
|
|
74
|
+
params: ParamsOf<Schema>,
|
|
75
|
+
event: H3Event,
|
|
76
|
+
) => ResponseOf<Schema> | Promise<ResponseOf<Schema>>,
|
|
77
|
+
): EventHandler<Record<string, never>, Promise<ResponseOf<Schema> | ErrorResponse>> {
|
|
78
|
+
return defineEventHandler(async (event) => {
|
|
79
|
+
try {
|
|
80
|
+
const params = await readParams(event);
|
|
81
|
+
const { valid, error } = validateSchema(schema, params);
|
|
82
|
+
if (!valid) {
|
|
83
|
+
throw createError({
|
|
84
|
+
statusCode: BAD_REQUEST,
|
|
85
|
+
statusMessage: "Bad Request",
|
|
86
|
+
message: error,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Validated against the schema the params type was generated from
|
|
90
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
91
|
+
const response = await handler(params as ParamsOf<Schema>, event);
|
|
92
|
+
checkResponse(schema, response);
|
|
93
|
+
return response;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return sendError(event, schema, error);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Register a route that cannot go through defineTypedEventHandler: non JSON request body (file upload) or non JSON response (stream). Errors are still sent as an ErrorResponse.
|
|
101
|
+
function defineRawEventHandler<Response>(
|
|
102
|
+
schema: RouteSchema,
|
|
103
|
+
handler: (event: H3Event) => Response | Promise<Response>,
|
|
104
|
+
): EventHandler<Record<string, never>, Promise<Response | ErrorResponse>> {
|
|
105
|
+
return defineEventHandler(async (event) => {
|
|
106
|
+
try {
|
|
107
|
+
return await handler(event);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return sendError(event, schema, error);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { defineRawEventHandler, defineTypedEventHandler };
|
package/shared/utils/types.ts
CHANGED
|
@@ -13,10 +13,18 @@ interface ValidationError {
|
|
|
13
13
|
error: string | null | undefined;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// Generated `*_schemas.d.ts` files tag each schema with phantom `__params`/`__response` types; untagged schemas fall back to untyped.
|
|
17
|
+
type ResponseOf<Schema> = Schema extends { readonly __response?: infer Response }
|
|
18
|
+
? Response
|
|
19
|
+
: unknown;
|
|
20
|
+
type ParamsOf<Schema> = Schema extends { readonly __params?: infer Params }
|
|
21
|
+
? Params
|
|
22
|
+
: Record<string, unknown>;
|
|
23
|
+
|
|
24
|
+
interface RequestHandlers<Response = unknown> {
|
|
17
25
|
readonly request_error_function?: (error: unknown) => void | Promise<void>;
|
|
18
26
|
// Return value is intentionally untyped: callers commonly return the Promise of a downstream call (e.g. a Dexie `.put()`, which resolves to a primary key) that this code chains/awaits but never inspects the resolved value of.
|
|
19
|
-
readonly response_function?: (response:
|
|
27
|
+
readonly response_function?: (response: Response) => unknown;
|
|
20
28
|
readonly response_error_function?: (response: unknown) => void | Promise<void>;
|
|
21
29
|
}
|
|
22
30
|
|
|
@@ -24,4 +32,11 @@ interface RequestHandlersWithValidation extends RequestHandlers {
|
|
|
24
32
|
readonly validation_error_function?: (error: ValidationError) => void;
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
export type {
|
|
35
|
+
export type {
|
|
36
|
+
JsonRpcSchema,
|
|
37
|
+
ParamsOf,
|
|
38
|
+
RequestHandlers,
|
|
39
|
+
RequestHandlersWithValidation,
|
|
40
|
+
ResponseOf,
|
|
41
|
+
ValidationError,
|
|
42
|
+
};
|
|
@@ -4,7 +4,7 @@ import type { JsonRpcSchema } from "./types.js";
|
|
|
4
4
|
|
|
5
5
|
function validateSchema(schema: JsonRpcSchema, body: unknown): { valid: boolean; error: string } {
|
|
6
6
|
const ajv = new Ajv();
|
|
7
|
-
const list_keywords = ["methods", "route", "max_retry", "rpc"];
|
|
7
|
+
const list_keywords = ["methods", "route", "max_retry", "rpc", "response"];
|
|
8
8
|
for (const keyword of list_keywords) {
|
|
9
9
|
ajv.addKeyword(keyword);
|
|
10
10
|
}
|