@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.
Files changed (62) hide show
  1. package/.build/opengeodeweb_front_schemas.js +200 -21
  2. package/.build/server/utils/typed_handler.js +63 -0
  3. package/.build/shared/utils/validate_schema.js +2 -1
  4. package/app/components/CrsSelector.vue +5 -5
  5. package/app/components/ExtensionSelector.vue +6 -12
  6. package/app/components/FileSelector.vue +2 -4
  7. package/app/components/MissingFilesSelector.vue +1 -1
  8. package/app/components/ObjectSelector.vue +1 -1
  9. package/app/components/ThresholdFilter.vue +1 -1
  10. package/app/components/Viewer/Generic/Model/BlocksOptions.vue +1 -1
  11. package/app/components/Viewer/Generic/Model/CornersOptions.vue +1 -1
  12. package/app/components/Viewer/Generic/Model/LinesOptions.vue +1 -1
  13. package/app/components/Viewer/Generic/Model/SurfacesOptions.vue +1 -1
  14. package/app/components/Viewer/Options/ColoringTypeSelector.vue +1 -1
  15. package/app/components/Viewer/Options/TextureItem.vue +5 -7
  16. package/app/composables/global_attribute_style.ts +1 -1
  17. package/app/composables/project_manager.ts +1 -1
  18. package/app/stores/app.ts +19 -17
  19. package/app/stores/back.ts +27 -21
  20. package/app/stores/cloud.ts +1 -1
  21. package/app/stores/infra.ts +1 -1
  22. package/app/stores/viewer.ts +4 -6
  23. package/app/utils/extension.ts +4 -4
  24. package/app/utils/import_workflow.ts +1 -1
  25. package/internal/stores/hybrid_viewer/base64.ts +6 -0
  26. package/internal/stores/hybrid_viewer/scene.ts +5 -1
  27. package/internal/utils/api_fetch.ts +9 -8
  28. package/opengeodeweb_front_schemas.json +252 -19
  29. package/opengeodeweb_front_typed_schemas.d.ts +225 -0
  30. package/opengeodeweb_front_typed_schemas.js +4 -0
  31. package/package.json +7 -5
  32. package/server/api/cloud/extensions/run.post.ts +9 -21
  33. package/server/api/cloud/extensions/schemas/run.json +42 -3
  34. package/server/api/local/app/kill.post.ts +4 -2
  35. package/server/api/local/app/project_folder_path.post.ts +6 -22
  36. package/server/api/local/app/run_back.post.ts +8 -23
  37. package/server/api/local/app/run_viewer.post.ts +8 -23
  38. package/server/api/local/app/schemas/kill.json +7 -1
  39. package/server/api/local/app/schemas/project_folder_path.json +19 -2
  40. package/server/api/local/app/schemas/run_back.json +31 -4
  41. package/server/api/local/app/schemas/run_viewer.json +31 -4
  42. package/server/api/local/extensions/kill.post.ts +8 -23
  43. package/server/api/local/extensions/run.post.ts +8 -22
  44. package/server/api/local/extensions/schemas/kill.json +20 -4
  45. package/server/api/local/extensions/schemas/run.json +42 -3
  46. package/server/api/local/extensions/schemas/upload.json +11 -1
  47. package/server/api/local/extensions/upload.put.ts +37 -29
  48. package/server/api/microservice/app/get_is_app_ready.get.ts +7 -13
  49. package/server/api/microservice/app/schemas/get_is_app_ready.json +14 -1
  50. package/server/api/microservice/app/schemas/set_app_base_url.json +17 -2
  51. package/server/api/microservice/app/schemas/set_back_base_url.json +17 -2
  52. package/server/api/microservice/app/schemas/set_is_app_ready.json +17 -2
  53. package/server/api/microservice/app/schemas/set_viewer_base_url.json +17 -2
  54. package/server/api/microservice/app/set_app_base_url.post.ts +8 -17
  55. package/server/api/microservice/app/set_back_base_url.post.ts +8 -16
  56. package/server/api/microservice/app/set_is_app_ready.post.ts +8 -17
  57. package/server/api/microservice/app/set_viewer_base_url.post.ts +8 -16
  58. package/server/api/microservice/extensions/download.post.ts +8 -21
  59. package/server/api/microservice/extensions/schemas/download.json +21 -4
  60. package/server/utils/typed_handler.ts +114 -0
  61. package/shared/utils/types.ts +18 -3
  62. package/shared/utils/validate_schema.ts +1 -1
package/app/stores/app.ts CHANGED
@@ -8,10 +8,15 @@ import { upload_file } from "@ogw_internal/utils/upload_file.js";
8
8
  import { useAppExtensions } from "./app_helpers/extension.js";
9
9
  import { useInfraStore } from "@ogw_front/stores/infra";
10
10
 
11
- import type { JsonRpcSchema, RequestHandlers } from "@ogw_shared/utils/types.js";
11
+ import type {
12
+ JsonRpcSchema,
13
+ ParamsOf,
14
+ RequestHandlers,
15
+ ResponseOf,
16
+ } from "@ogw_shared/utils/types.js";
12
17
  import type { StateTree } from "pinia";
13
18
 
14
- import opengeodeweb_front_schemas from "@geode/opengeodeweb-front/opengeodeweb_front_schemas.json" with { type: "json" };
19
+ import opengeodeweb_front_schemas from "@geode/opengeodeweb-front/opengeodeweb_front_typed_schemas.js";
15
20
 
16
21
  // The `share` defineStore option (used by every store in this codebase) is implemented by a runtime pinia plugin outside this package's type surface; this augmentation only teaches the type checker about the option shape already used at each defineStore call site.
17
22
  declare module "pinia" {
@@ -170,29 +175,30 @@ export const useAppStore = defineStore("app", () => {
170
175
  return result;
171
176
  }
172
177
 
173
- // `TResult` is asserted, not verified, at the single `return result as TResult` boundary below: the backend response is only checked against `schema` at runtime, so callers' `TResult` is a contract with the schema, not something this function can prove.
178
+ // The response type comes from the schema's generated `response` type, asserted at the single `as ResponseOf<Schema>` boundary below: the server checks its responses against that schema outside production.
174
179
  // oxlint-disable-next-line unicorn/consistent-function-scoping
175
- // oxlint-disable-next-line unicorn/consistent-function-scoping
176
- async function request<TResult = unknown>(
177
- { schema, params }: { schema: JsonRpcSchema; params?: Record<string, unknown> },
178
- callbacks: RequestHandlers = {},
179
- ): Promise<TResult> {
180
+ async function request<Schema extends JsonRpcSchema>(
181
+ { schema, params }: { schema: Schema; params?: ParamsOf<Schema> },
182
+ callbacks: RequestHandlers<ResponseOf<Schema>> = {},
183
+ ): Promise<ResponseOf<Schema>> {
184
+ const rpc_schema: JsonRpcSchema = schema;
180
185
  const result = await api_fetch(
181
186
  { $id: "app", base_url: base_url.value, start_request, stop_request },
182
187
  // The app store is only ever used with HTTP ("front") schemas, which always carry `methods`; the wider JsonRpcSchema param above is kept as-is to match this action's public signature (e.g. relayed from get_version-style callers that only know about the shared, looser schema shape).
183
188
  // oxlint-disable-next-line no-unsafe-type-assertion -- narrowing optional `methods` to required is safe here; see comment above.
184
- { schema: schema as JsonRpcSchema & { methods: string[] }, params },
189
+ { schema: rpc_schema as JsonRpcSchema & { methods: string[] }, params: params ?? {} },
185
190
  {
186
191
  ...callbacks,
187
192
  response_function: async (response: unknown) => {
188
193
  if (callbacks.response_function) {
189
- await callbacks.response_function(response);
194
+ // oxlint-disable-next-line no-unsafe-type-assertion -- this is the trusted API boundary; see comment above.
195
+ await callbacks.response_function(response as ResponseOf<Schema>);
190
196
  }
191
197
  },
192
198
  },
193
199
  );
194
200
  // oxlint-disable-next-line no-unsafe-type-assertion -- this is the trusted API boundary; see comment above.
195
- return result as TResult;
201
+ return result as ResponseOf<Schema>;
196
202
  }
197
203
 
198
204
  const projectFolderPath = ref("");
@@ -204,12 +210,8 @@ export const useAppStore = defineStore("app", () => {
204
210
  const result = await request(
205
211
  { schema, params },
206
212
  {
207
- response_function: (response: unknown) => {
208
- // oxlint-disable-next-line no-unsafe-type-assertion
209
- const { projectFolderPath: newProjectFolderPath } = response as {
210
- projectFolderPath: string;
211
- };
212
- projectFolderPath.value = newProjectFolderPath;
213
+ response_function: (response) => {
214
+ projectFolderPath.value = response.projectFolderPath;
213
215
  },
214
216
  },
215
217
  );
@@ -1,15 +1,20 @@
1
1
  import { getRestApiPort, getRestApiProtocol, isCloudMode } from "@ogw_front/utils/stores";
2
2
  import { Status } from "@ogw_front/utils/status";
3
3
  import { api_fetch } from "@ogw_internal/utils/api_fetch";
4
- import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json";
4
+ import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js";
5
5
  import { upload_file } from "@ogw_internal/utils/upload_file.js";
6
6
  import { useAppStore } from "@ogw_front/stores/app";
7
7
  import { useFeedbackStore } from "@ogw_front/stores/feedback";
8
8
  import { useInfraStore } from "@ogw_front/stores/infra";
9
9
 
10
- import type { JsonRpcSchema, RequestHandlers } from "@ogw_shared/utils/types.js";
10
+ import type {
11
+ JsonRpcSchema,
12
+ ParamsOf,
13
+ RequestHandlers,
14
+ ResponseOf,
15
+ } from "@ogw_shared/utils/types.js";
11
16
 
12
- import opengeodeweb_front_schemas from "@geode/opengeodeweb-front/opengeodeweb_front_schemas.json" with { type: "json" };
17
+ import opengeodeweb_front_schemas from "@geode/opengeodeweb-front/opengeodeweb_front_typed_schemas.js";
13
18
 
14
19
  const MILLISECONDS_IN_SECOND = 1000;
15
20
  const DEFAULT_PING_INTERVAL_SECONDS = 10;
@@ -81,7 +86,7 @@ export const useBackStore = defineStore("back", {
81
86
  stop_request() {
82
87
  this.request_counter -= 1;
83
88
  },
84
- async launch(args: Record<string, unknown>) {
89
+ async launch(args: { projectFolderPath: string }) {
85
90
  const appStore = useAppStore();
86
91
  const { COMMAND_BACK, NUXT_ROOT_PATH } = useRuntimeConfig().public;
87
92
  const schema = opengeodeweb_front_schemas.api.local.app.run_back;
@@ -90,15 +95,8 @@ export const useBackStore = defineStore("back", {
90
95
  const result = await appStore.request(
91
96
  { schema, params },
92
97
  {
93
- response_function: (response: unknown) => {
94
- if (
95
- typeof response === "object" &&
96
- response !== null &&
97
- "port" in response &&
98
- (typeof response.port === "string" || typeof response.port === "number")
99
- ) {
100
- this.default_local_port = String(response.port);
101
- }
98
+ response_function: (response) => {
99
+ this.default_local_port = String(response.port);
102
100
  },
103
101
  },
104
102
  );
@@ -108,27 +106,35 @@ export const useBackStore = defineStore("back", {
108
106
  this.set_ping();
109
107
  await Promise.resolve();
110
108
  },
111
- async request(
112
- { schema, params = {} }: { schema: JsonRpcSchema; params?: Record<string, unknown> },
113
- callbacks: RequestHandlers = {},
114
- ) {
109
+ async request<Schema extends JsonRpcSchema>(
110
+ { schema, params }: { schema: Schema; params?: ParamsOf<Schema> },
111
+ callbacks: RequestHandlers<ResponseOf<Schema>> = {},
112
+ ): Promise<ResponseOf<Schema>> {
113
+ const rpc_schema: JsonRpcSchema = schema;
115
114
  const result = await api_fetch(
116
115
  this,
117
116
  // The back store is only ever used with HTTP ("front"/"back") schemas,
118
117
  // Which always carry `methods`; the wider JsonRpcSchema param above is
119
118
  // Kept as-is to match this action's public signature.
120
- // oxlint-disable-next-line typescript/no-unsafe-type-assertion
121
- { schema: schema as JsonRpcSchema & { methods: string[] }, params, headers: {} },
119
+ {
120
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
121
+ schema: rpc_schema as JsonRpcSchema & { methods: string[] },
122
+ params: params ?? {},
123
+ headers: {},
124
+ },
122
125
  {
123
126
  ...callbacks,
124
127
  response_function: async (response: unknown) => {
125
128
  if (callbacks.response_function) {
126
- await callbacks.response_function(response);
129
+ // The back validates its responses against the schema `response` it was generated from.
130
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
131
+ await callbacks.response_function(response as ResponseOf<Schema>);
127
132
  }
128
133
  },
129
134
  },
130
135
  );
131
- return result;
136
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
137
+ return result as ResponseOf<Schema>;
132
138
  },
133
139
  async upload(file: File, callbacks: RequestHandlers = {}) {
134
140
  const schema = back_schemas.opengeodeweb_back.upload_file;
@@ -2,7 +2,7 @@ import { clearCloudUrlParam, getCloudUrlParam } from "@ogw_front/utils/cloud";
2
2
  import type { RunCloudResponse } from "@geode/cloud-api/types";
3
3
  import { Status } from "@ogw_front/utils/status";
4
4
  import { api_fetch } from "@ogw_internal/utils/api_fetch";
5
- import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json";
5
+ import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js";
6
6
  import cloud_api_schemas from "@geode/cloud-api/cloud_api_schemas.json";
7
7
  import { setAppBaseUrl } from "@ogw_shared/scripts";
8
8
  import { useAPIStore } from "@ogw_front/stores/api";
@@ -107,6 +107,6 @@ export interface Microservice {
107
107
  $id: string;
108
108
  status?: string;
109
109
  is_busy?: boolean;
110
- launch?: (params: Record<string, unknown>) => Promise<unknown>;
110
+ launch?: (params: { projectFolderPath: string }) => Promise<unknown>;
111
111
  connect: () => Promise<void>;
112
112
  }
@@ -4,7 +4,7 @@ import "@kitware/vtk.js/Rendering/OpenGL/Profiles/Geometry";
4
4
  import type { Vector3 } from "@kitware/vtk.js/types";
5
5
  import { connectImageStream } from "@kitware/vtk.js/Rendering/Misc/RemoteView";
6
6
  import { initWebSocketClient } from "@ogw_internal/utils/ws_client";
7
- import opengeodeweb_front_schemas from "@geode/opengeodeweb-front/opengeodeweb_front_schemas.json" with { type: "json" };
7
+ import opengeodeweb_front_schemas from "@geode/opengeodeweb-front/opengeodeweb_front_typed_schemas.js";
8
8
  import opengeodeweb_viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json" with { type: "json" };
9
9
 
10
10
  // Local imports
@@ -150,7 +150,7 @@ export const useViewerStore = defineStore(
150
150
  }
151
151
  });
152
152
  }
153
- async function launch(args: Readonly<{ projectFolderPath?: string }> = {}): Promise<unknown> {
153
+ async function launch(args: Readonly<{ projectFolderPath: string }>): Promise<unknown> {
154
154
  const appStore = useAppStore();
155
155
  const { COMMAND_VIEWER, NUXT_ROOT_PATH } = useRuntimeConfig().public;
156
156
  const schema = opengeodeweb_front_schemas.api.local.app.run_viewer;
@@ -165,10 +165,8 @@ export const useViewerStore = defineStore(
165
165
  params,
166
166
  },
167
167
  {
168
- response_function: (response: unknown) => {
169
- // oxlint-disable-next-line no-unsafe-type-assertion -- response shape is defined by the run_viewer schema.
170
- const { port: viewerPort } = response as { port: string };
171
- default_local_port.value = viewerPort;
168
+ response_function: (response) => {
169
+ default_local_port.value = String(response.port);
172
170
  },
173
171
  },
174
172
  );
@@ -7,14 +7,14 @@ import type { RegisterableStore, useAppStore } from "@ogw_front/stores/app";
7
7
  import type { Microservice } from "@ogw_front/stores/infra";
8
8
  import { consola } from "consola";
9
9
  import { isCloudMode } from "@ogw_front/utils/stores";
10
- import opengeodeweb_front_schemas from "@geode/opengeodeweb-front/opengeodeweb_front_schemas.json" with { type: "json" };
10
+ import opengeodeweb_front_schemas from "@geode/opengeodeweb-front/opengeodeweb_front_typed_schemas.js";
11
11
 
12
12
  interface ExtensionDescriptor {
13
13
  id: string;
14
14
  name: string;
15
15
  version: string;
16
16
  frontendContent: string;
17
- port: string;
17
+ port: number;
18
18
  }
19
19
 
20
20
  interface DownloadExtensionParams {
@@ -48,7 +48,7 @@ async function runExtensions(): Promise<{ extensionsArray: ExtensionDescriptor[]
48
48
  projectFolderPath,
49
49
  projectName,
50
50
  };
51
- const result = await appStore.request<{ extensionsArray: ExtensionDescriptor[] }>({
51
+ const result = await appStore.request({
52
52
  schema,
53
53
  params,
54
54
  });
@@ -92,7 +92,7 @@ async function registerRunningExtensions(): Promise<RegisteredExtension[]> {
92
92
  type: "application/javascript",
93
93
  });
94
94
  const blobUrl = URL.createObjectURL(blob);
95
- const extensionModule = await appStore.loadExtension(blobUrl, port);
95
+ const extensionModule = await appStore.loadExtension(blobUrl, String(port));
96
96
  consola.info("[ExtensionManager] Extension loaded:", id);
97
97
  const storeFactory = extensionModule.metadata.store;
98
98
  const store = storeFactory();
@@ -1,6 +1,6 @@
1
1
  // oxlint-disable promise/prefer-await-to-then
2
2
  // Third party imports
3
- import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json";
3
+ import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js";
4
4
  import { consola } from "consola";
5
5
 
6
6
  // Local imports
@@ -0,0 +1,6 @@
1
+ // Decodes a base64 string (the back's binary_light_viewable) into raw bytes for vtk.js readers.
2
+ // Uint8Array.fromBase64 would do this natively but is unavailable in Node 22, which runs the tests.
3
+ export function base64ToArrayBuffer(b64: string): ArrayBuffer {
4
+ // Atob returns a "binary string": one char per byte, so each code point is the byte value.
5
+ return Uint8Array.from(atob(b64), (char) => char.codePointAt(0) ?? 0).buffer;
6
+ }
@@ -1,6 +1,7 @@
1
1
  import type { HybridDb, vtkActor as VtkActorInstance } from "./vtk_types";
2
2
  import { requireRenderWindow, useHybridViewerCore } from "./core";
3
3
  import { ACTOR_COLOR } from "./constants";
4
+ import { base64ToArrayBuffer } from "./base64";
4
5
  import { useDataStore } from "@ogw_front/stores/data";
5
6
  import { useViewerStore } from "@ogw_front/stores/viewer";
6
7
  import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
@@ -25,8 +26,11 @@ const useHybridViewerScene = createSharedComposable(() => {
25
26
  return;
26
27
  }
27
28
  const value = await dataStore.item(id);
29
+ if (value.binary_light_viewable === undefined) {
30
+ return;
31
+ }
28
32
  const reader = vtkXMLPolyDataReader();
29
- reader.parseAsArrayBuffer(new TextEncoder().encode(value.binary_light_viewable).buffer);
33
+ reader.parseAsArrayBuffer(base64ToArrayBuffer(value.binary_light_viewable));
30
34
  const actor = vtkActor();
31
35
  const mapper = vtkMapper();
32
36
  // oxlint-disable-next-line no-unsafe-type-assertion -- vtk.js's algorithm interface types getOutputData as `any`; this reader always produces polydata.
@@ -1,5 +1,6 @@
1
1
  import type { JsonRpcSchema, RequestHandlersWithValidation } from "@ogw_shared/utils/types.js";
2
2
  import { endRequestLog, startRequestLog } from "@ogw_front/utils/log";
3
+ import type { ErrorResponse } from "@geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js";
3
4
  import { consola } from "consola";
4
5
  import { fetchSchema } from "@ogw_shared/utils/fetch_schema";
5
6
  import { useFeedbackStore } from "@ogw_front/stores/feedback";
@@ -15,7 +16,7 @@ interface Microservice {
15
16
  interface ApiFetchParams {
16
17
  // This function always forwards this schema to fetchSchema, which requires the HTTP-flavored `methods` array (as opposed to the websocket `rpc` field).
17
18
  schema: JsonRpcSchema & { methods: string[] };
18
- params?: Record<string, unknown>;
19
+ params?: object;
19
20
  headers?: Record<string, string>;
20
21
  }
21
22
 
@@ -26,11 +27,9 @@ interface FetchErrorLike {
26
27
  stack?: string;
27
28
  }
28
29
 
29
- interface FetchErrorResponseLike {
30
- status?: number;
31
- name?: string;
32
- description?: string;
33
- }
30
+ type FetchErrorResponseLike = { status?: number } & Partial<
31
+ Pick<ErrorResponse, "name" | "description">
32
+ >;
34
33
 
35
34
  function isRecord(value: unknown): value is Record<string, unknown> {
36
35
  return typeof value === "object" && value !== null;
@@ -51,10 +50,12 @@ function toFetchErrorResponseLike(response: unknown): FetchErrorResponseLike {
51
50
  if (!isRecord(response)) {
52
51
  return {};
53
52
  }
53
+ // Ofetch puts the parsed body (an ErrorResponse for Flask microservices) in `_data`
54
+ const body = isRecord(response._data) ? response._data : response;
54
55
  return {
55
56
  status: typeof response.status === "number" ? response.status : undefined,
56
- name: typeof response.name === "string" ? response.name : undefined,
57
- description: typeof response.description === "string" ? response.description : undefined,
57
+ name: typeof body.name === "string" ? body.name : undefined,
58
+ description: typeof body.description === "string" ? body.description : undefined,
58
59
  };
59
60
  }
60
61