@geode/opengeodeweb-front 10.36.0 → 10.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/app/components/Screenshot.vue +6 -4
  2. package/app/components/ViewToolbar.vue +4 -2
  3. package/app/components/Viewer/EdgedCurve/SpecificEdgesOptions.vue +7 -4
  4. package/app/components/Viewer/Generic/Mesh/CellsOptions.vue +10 -4
  5. package/app/components/Viewer/Generic/Mesh/EdgesOptions.vue +7 -4
  6. package/app/components/Viewer/Generic/Mesh/PointsOptions.vue +5 -2
  7. package/app/components/Viewer/Generic/Mesh/PolygonsOptions.vue +10 -6
  8. package/app/components/Viewer/Generic/Mesh/PolyhedraOptions.vue +7 -6
  9. package/app/components/Viewer/Generic/Model/BlocksOptions.vue +9 -18
  10. package/app/components/Viewer/Generic/Model/CornersOptions.vue +5 -10
  11. package/app/components/Viewer/Generic/Model/EdgesOptions.vue +3 -0
  12. package/app/components/Viewer/Generic/Model/LinesOptions.vue +9 -17
  13. package/app/components/Viewer/Generic/Model/SurfacesOptions.vue +8 -18
  14. package/app/components/Viewer/ObjectTree/Views/ModelTree.vue +1 -5
  15. package/app/components/Viewer/PointSet/SpecificPointsOptions.vue +11 -2
  16. package/app/components/Viewer/Solid/SpecificPolyhedraOptions.vue +7 -6
  17. package/app/components/Viewer/Surface/PolygonsOptions.vue +10 -6
  18. package/app/composables/hover_highlight.ts +5 -4
  19. package/app/composables/project_manager.ts +1 -1
  20. package/app/composables/use_overlapping_picker.ts +4 -73
  21. package/app/composables/use_quick_colormap.ts +4 -14
  22. package/app/stores/back.ts +4 -10
  23. package/app/stores/camera_manager.ts +1 -1
  24. package/app/stores/data.ts +1 -1
  25. package/app/stores/viewer.ts +33 -19
  26. package/internal/stores/data_style/mesh/cells/cell.ts +26 -23
  27. package/internal/stores/data_style/mesh/cells/color.ts +8 -6
  28. package/internal/stores/data_style/mesh/cells/index.ts +13 -6
  29. package/internal/stores/data_style/mesh/cells/textures.ts +17 -6
  30. package/internal/stores/data_style/mesh/cells/vertex.ts +29 -23
  31. package/internal/stores/data_style/mesh/cells/visibility.ts +3 -6
  32. package/internal/stores/data_style/mesh/edges/color.ts +8 -6
  33. package/internal/stores/data_style/mesh/edges/edge.ts +26 -23
  34. package/internal/stores/data_style/mesh/edges/index.ts +13 -3
  35. package/internal/stores/data_style/mesh/edges/vertex.ts +29 -23
  36. package/internal/stores/data_style/mesh/edges/visibility.ts +3 -6
  37. package/internal/stores/data_style/mesh/edges/width.ts +3 -3
  38. package/internal/stores/data_style/mesh/index.ts +24 -11
  39. package/internal/stores/data_style/mesh/points/color.ts +8 -6
  40. package/internal/stores/data_style/mesh/points/index.ts +13 -6
  41. package/internal/stores/data_style/mesh/points/size.ts +3 -3
  42. package/internal/stores/data_style/mesh/points/vertex.ts +29 -23
  43. package/internal/stores/data_style/mesh/points/visibility.ts +5 -11
  44. package/internal/stores/data_style/mesh/polygons/color.ts +8 -6
  45. package/internal/stores/data_style/mesh/polygons/index.ts +13 -9
  46. package/internal/stores/data_style/mesh/polygons/polygon.ts +26 -23
  47. package/internal/stores/data_style/mesh/polygons/textures.ts +17 -6
  48. package/internal/stores/data_style/mesh/polygons/vertex.ts +26 -23
  49. package/internal/stores/data_style/mesh/polygons/visibility.ts +3 -6
  50. package/internal/stores/data_style/mesh/polyhedra/color.ts +8 -6
  51. package/internal/stores/data_style/mesh/polyhedra/index.ts +9 -5
  52. package/internal/stores/data_style/mesh/polyhedra/polyhedron.ts +28 -23
  53. package/internal/stores/data_style/mesh/polyhedra/vertex.ts +26 -23
  54. package/internal/stores/data_style/mesh/polyhedra/visibility.ts +3 -6
  55. package/internal/stores/data_style/model/blocks/color.ts +8 -6
  56. package/internal/stores/data_style/model/blocks/index.ts +9 -22
  57. package/internal/stores/data_style/model/blocks/polyhedron.ts +28 -23
  58. package/internal/stores/data_style/model/blocks/vertex.ts +26 -23
  59. package/internal/stores/data_style/model/blocks/visibility.ts +3 -3
  60. package/internal/stores/data_style/model/color.ts +10 -7
  61. package/internal/stores/data_style/model/common.ts +36 -20
  62. package/internal/stores/data_style/model/corners/color.ts +8 -6
  63. package/internal/stores/data_style/model/corners/index.ts +8 -14
  64. package/internal/stores/data_style/model/corners/vertex.ts +26 -23
  65. package/internal/stores/data_style/model/corners/visibility.ts +3 -3
  66. package/internal/stores/data_style/model/edges/visibility.ts +8 -8
  67. package/internal/stores/data_style/model/lines/color.ts +8 -6
  68. package/internal/stores/data_style/model/lines/edge.ts +26 -23
  69. package/internal/stores/data_style/model/lines/index.ts +9 -22
  70. package/internal/stores/data_style/model/lines/vertex.ts +26 -23
  71. package/internal/stores/data_style/model/lines/visibility.ts +3 -3
  72. package/internal/stores/data_style/model/points/index.ts +14 -4
  73. package/internal/stores/data_style/model/points/size.ts +3 -3
  74. package/internal/stores/data_style/model/points/visibility.ts +3 -6
  75. package/internal/stores/data_style/model/surfaces/color.ts +8 -6
  76. package/internal/stores/data_style/model/surfaces/index.ts +9 -22
  77. package/internal/stores/data_style/model/surfaces/polygon.ts +29 -23
  78. package/internal/stores/data_style/model/surfaces/vertex.ts +26 -23
  79. package/internal/stores/data_style/model/surfaces/visibility.ts +3 -3
  80. package/internal/stores/data_style/model/visibility.ts +1 -1
  81. package/internal/stores/data_style/when_defined.ts +13 -0
  82. package/internal/stores/hybrid_viewer/camera.ts +14 -15
  83. package/internal/stores/hybrid_viewer/core.ts +1 -1
  84. package/internal/stores/hybrid_viewer/filters.ts +12 -6
  85. package/internal/stores/hybrid_viewer/highlight.ts +9 -18
  86. package/internal/stores/hybrid_viewer/ruler.ts +10 -13
  87. package/internal/stores/hybrid_viewer/scene.ts +1 -1
  88. package/internal/utils/viewer_call.ts +1 -1
  89. package/package.json +1 -1
  90. package/server/utils/ws_client.ts +4 -4
  91. package/shared/utils/call_raw.ts +3 -3
  92. package/shared/utils/call_schema.ts +10 -8
  93. package/shared/utils/types.ts +6 -0
@@ -9,7 +9,7 @@ import { requireRenderWindow, useHybridViewerCore } from "./core";
9
9
  import { dot } from "@kitware/vtk.js/Common/Core/Math";
10
10
  import { useHybridViewerScene } from "./scene";
11
11
  import { useViewerStore } from "@ogw_front/stores/viewer";
12
- import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
12
+ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_typed_schemas.js";
13
13
 
14
14
  const BUMP_MULTIPLIER = 0.2;
15
15
  const ALIGNMENT_THRESHOLD = 0.9;
@@ -210,25 +210,24 @@ const useHybridViewerCamera = createSharedComposable(() => {
210
210
  syncRemoteCamera();
211
211
  }
212
212
 
213
- async function performFocusCameraOnObject(id: string, block_ids: string[] = []): Promise<void> {
213
+ async function performFocusCameraOnObject(id: string, block_ids: number[] = []): Promise<void> {
214
214
  const { genericRenderWindow } = useHybridViewerCore();
215
215
  const { hybridDb } = useHybridViewerScene();
216
216
  if (!hybridDb[id]) {
217
217
  return;
218
218
  }
219
219
  const viewerStore = useViewerStore();
220
- const bounds: [number, number, number, number, number, number] =
221
- block_ids.length > 0
222
- ? toBounds(
223
- await viewerStore.request({
224
- schema: viewer_schemas.opengeodeweb_viewer.model.get_blocks_bounds,
225
- params: {
226
- id,
227
- block_ids,
228
- },
229
- }),
230
- )
231
- : hybridDb[id].actor.getBounds();
220
+ let bounds: [number, number, number, number, number, number] = hybridDb[id].actor.getBounds();
221
+ if (block_ids.length > 0) {
222
+ const response = await viewerStore.request({
223
+ schema: viewer_schemas.opengeodeweb_viewer.model.get_blocks_bounds,
224
+ params: {
225
+ id,
226
+ block_ids,
227
+ },
228
+ });
229
+ bounds = toBounds(response.bounds);
230
+ }
232
231
  const renderer = requireRenderWindow(genericRenderWindow).getRenderer();
233
232
  const camera = renderer.getActiveCamera();
234
233
  const startOptions = getCameraOptions(camera);
@@ -238,7 +237,7 @@ const useHybridViewerCamera = createSharedComposable(() => {
238
237
  performSetCamera(targetOptions);
239
238
  }
240
239
 
241
- async function focusCameraOnObject(id: string, block_ids: string[] = []): Promise<void> {
240
+ async function focusCameraOnObject(id: string, block_ids: number[] = []): Promise<void> {
242
241
  await performFocusCameraOnObject(id, block_ids);
243
242
  }
244
243
 
@@ -2,7 +2,7 @@ import { Status } from "@ogw_front/utils/status";
2
2
  import type { ViewStreamLike } from "./vtk_types";
3
3
  import type { vtkGenericRenderWindow as VtkGenericRenderWindow } from "@kitware/vtk.js/Rendering/Misc/GenericRenderWindow";
4
4
  import { useViewerStore } from "@ogw_front/stores/viewer";
5
- import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
5
+ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_typed_schemas.js";
6
6
 
7
7
  interface GenericRenderWindowHolder {
8
8
  value?: VtkGenericRenderWindow;
@@ -1,9 +1,14 @@
1
+ import viewer_schemas, {
2
+ type ViewerClippingPlanesParams,
3
+ } from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_typed_schemas.js";
1
4
  import type { SliceAxis } from "@ogw_front/utils/slice";
2
5
  import { useHybridViewerCore } from "./core";
3
6
  import { useViewerStore } from "@ogw_front/stores/viewer";
4
- import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
5
7
 
6
- async function setClippingPlanes(ids: string[], planes: unknown): Promise<void> {
8
+ async function setClippingPlanes(
9
+ ids: string[],
10
+ planes: ViewerClippingPlanesParams["planes"],
11
+ ): Promise<void> {
7
12
  const viewerStore = useViewerStore();
8
13
  const { remoteRender } = useHybridViewerCore();
9
14
  const schema = viewer_schemas.opengeodeweb_viewer.viewer.clipping_planes;
@@ -42,13 +47,14 @@ async function setSlice(
42
47
  ids,
43
48
  slices,
44
49
  };
45
- const response = await viewerStore.request({
50
+ const {
51
+ max_indices: [max_i = 0, max_j = 0, max_k = 0],
52
+ } = await viewerStore.request({
46
53
  schema,
47
54
  params,
48
55
  });
49
56
  await remoteRender();
50
- // oxlint-disable-next-line no-unsafe-type-assertion -- response shape is defined by the slice schema.
51
- return (response as { max_indices: [number, number, number] }).max_indices;
57
+ return [max_i, max_j, max_k];
52
58
  }
53
59
  interface ThresholdAttribute {
54
60
  name: string;
@@ -72,7 +78,7 @@ async function setThreshold(ids: string[], attribute?: ThresholdAttribute): Prom
72
78
  await remoteRender();
73
79
  }
74
80
  function useHybridViewerFilters(): {
75
- setClippingPlanes: (ids: string[], planes: unknown) => Promise<void>;
81
+ setClippingPlanes: (ids: string[], planes: ViewerClippingPlanesParams["planes"]) => Promise<void>;
76
82
  setShrink: (ids: string[], shrink_factor: number) => Promise<void>;
77
83
  setSlice: (
78
84
  ids: string[],
@@ -1,25 +1,19 @@
1
1
  import { HOVER_DEBOUNCE_MS, HOVER_TIMEOUT_MS } from "./constants";
2
2
  import type { HoverComponentInfo, HoverData } from "./vtk_types";
3
+ import viewer_schemas, {
4
+ type PickedFieldType,
5
+ type ViewerHighlightResponse,
6
+ } from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_typed_schemas.js";
3
7
  import type { IndexableType } from "dexie";
4
8
  import { database } from "@ogw_internal/database/database.js";
5
9
  import { useHybridViewerCore } from "./core";
6
10
  import { useHybridViewerScene } from "./scene";
7
11
  import { useViewerStore } from "@ogw_front/stores/viewer";
8
- import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
9
-
10
- // The dynamic/RPC-shaped payload of the viewer's "highlight" schema response.
11
- interface HighlightResponse {
12
- id?: string;
13
- picked_id?: number;
14
- field_type?: string;
15
- geode_id?: string | null;
16
- attributes?: Record<string, unknown>;
17
- }
18
12
 
19
13
  // Shared via createSharedComposable (rather than merged into the parent hybridViewer store) so sibling slices, e.g. ruler.ts and viewport.ts, can read hover state/clearHoverHighlight/hoverHighlight directly without importing the parent store and creating a cycle. A Pinia store would work too but its $id/$patch/... properties would leak into the composed store's spread and collapse its inferred type.
20
14
  const useHybridViewerHighlight = createSharedComposable(() => {
21
15
  const is_hover_highlight = ref(false);
22
- const hover_highlight_field_type = ref("CELL");
16
+ const hover_highlight_field_type = ref<PickedFieldType>("CELL");
23
17
  const hoverData = ref<HoverData | undefined>(undefined);
24
18
  const hoverPosition = ref({
25
19
  x: 0,
@@ -39,7 +33,7 @@ const useHybridViewerHighlight = createSharedComposable(() => {
39
33
 
40
34
  function requestHoverHighlight(
41
35
  event: MouseEvent,
42
- onResponse: (response: unknown) => void | Promise<void>,
36
+ onResponse: (response: ViewerHighlightResponse) => void | Promise<void>,
43
37
  ): void {
44
38
  if (!is_hover_highlight.value) {
45
39
  return;
@@ -85,12 +79,9 @@ const useHybridViewerHighlight = createSharedComposable(() => {
85
79
  x: event.clientX,
86
80
  y: event.clientY,
87
81
  };
88
- requestHoverHighlight(event, async (rawResponse: unknown) => {
89
- // oxlint-disable-next-line no-unsafe-type-assertion -- trusted viewer RPC response boundary.
90
- const response = rawResponse as HighlightResponse | undefined;
82
+ requestHoverHighlight(event, async (response) => {
91
83
  if (
92
84
  !is_hover_highlight.value ||
93
- response === undefined ||
94
85
  response.id === undefined ||
95
86
  response.picked_id === undefined ||
96
87
  response.picked_id === -1
@@ -115,7 +106,7 @@ const useHybridViewerHighlight = createSharedComposable(() => {
115
106
  modelName = modelRecord.name;
116
107
  }
117
108
  const modelComponentsTable = database.model_components;
118
- if (response.geode_id !== undefined && response.geode_id !== null && modelComponentsTable) {
109
+ if (response.geode_id !== undefined && modelComponentsTable) {
119
110
  const components = modelComponentsTable.where("[id+geode_id]");
120
111
  const query = components.equals([response.id, response.geode_id] as IndexableType);
121
112
  const component = (await query.first()) as
@@ -132,7 +123,7 @@ const useHybridViewerHighlight = createSharedComposable(() => {
132
123
  const newHoverData: HoverData = {
133
124
  modelId: response.id,
134
125
  modelName,
135
- blockName: response.geode_id ?? undefined,
126
+ blockName: response.geode_id,
136
127
  pickedId: response.picked_id,
137
128
  fieldType: response.field_type,
138
129
  component: componentInfo,
@@ -1,12 +1,14 @@
1
+ import viewer_schemas, {
2
+ type PickedFieldType,
3
+ } from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_typed_schemas.js";
1
4
  import { useHybridViewerCore } from "./core";
2
5
  import { useHybridViewerHighlight } from "./highlight";
3
6
  import { useHybridViewerScene } from "./scene";
4
7
  import { useViewerStore } from "@ogw_front/stores/viewer";
5
- import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
6
8
 
7
9
  interface RulerHoverState {
8
10
  active: boolean;
9
- fieldType: string;
11
+ fieldType: PickedFieldType;
10
12
  }
11
13
 
12
14
  function useHybridViewerRuler(): {
@@ -63,11 +65,8 @@ function useHybridViewerRuler(): {
63
65
  const { remoteRender } = useHybridViewerCore();
64
66
  const viewerStore = useViewerStore();
65
67
  const schema = viewer_schemas.opengeodeweb_viewer.viewer.ruler;
66
- // oxlint-disable-next-line no-unsafe-type-assertion -- response shape is defined by the ruler schema.
67
- const response = (await viewerStore.request({ schema, params: { points } })) as {
68
- distance?: number;
69
- };
70
- ruler_distance.value = response.distance;
68
+ const { distance } = await viewerStore.request({ schema, params: { points } });
69
+ ruler_distance.value = distance;
71
70
  await remoteRender();
72
71
  }
73
72
 
@@ -80,14 +79,12 @@ function useHybridViewerRuler(): {
80
79
  const params = {
81
80
  x: Math.round(x),
82
81
  y: Math.round(y),
83
- field_type: "POINT",
82
+ field_type: "POINT" as const,
84
83
  ids: Object.keys(hybridDb),
85
84
  };
86
- // oxlint-disable-next-line no-unsafe-type-assertion -- response shape is defined by the highlight schema.
87
- const response = (await viewerStore.request({ schema, params })) as {
88
- attributes?: { coordinates?: number[] };
89
- };
90
- coords = response.attributes?.coordinates;
85
+ const { attributes } = await viewerStore.request({ schema, params });
86
+ const coordinates = attributes?.coordinates;
87
+ coords = Array.isArray(coordinates) ? coordinates : undefined;
91
88
  } else {
92
89
  coords = await viewerStore.pick_world_position(x, y);
93
90
  }
@@ -4,7 +4,7 @@ import { ACTOR_COLOR } from "./constants";
4
4
  import { base64ToArrayBuffer } from "./base64";
5
5
  import { useDataStore } from "@ogw_front/stores/data";
6
6
  import { useViewerStore } from "@ogw_front/stores/viewer";
7
- import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
7
+ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_typed_schemas.js";
8
8
  import { newInstance as vtkActor } from "@kitware/vtk.js/Rendering/Core/Actor";
9
9
  import { newInstance as vtkMapper } from "@kitware/vtk.js/Rendering/Core/Mapper";
10
10
  import type vtkPolyData from "@kitware/vtk.js/Common/DataModel/PolyData";
@@ -11,7 +11,7 @@ interface ViewerMicroservice extends Microservice {
11
11
 
12
12
  interface ViewerCallParams {
13
13
  schema: JsonRpcSchema;
14
- params?: Record<string, unknown>;
14
+ params?: object;
15
15
  timeout?: number;
16
16
  }
17
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geode/opengeodeweb-front",
3
- "version": "10.36.0",
3
+ "version": "10.37.0",
4
4
  "description": "OpenSource Vue/Nuxt/Pinia/Vuetify framework for web applications",
5
5
  "homepage": "https://github.com/Geode-solutions/OpenGeodeWeb-Front",
6
6
  "bugs": {
@@ -34,11 +34,11 @@ function rawDataToString(raw: WebSocket.RawData): string {
34
34
  }
35
35
 
36
36
  interface ServerWsRpcClient {
37
- call: (rpc: string, params?: Record<string, unknown>) => Promise<unknown>;
37
+ call: (rpc: string, params?: object) => Promise<unknown>;
38
38
  close: () => void;
39
39
  getConnection: () => {
40
40
  getSession: () => {
41
- call: (rpc: string, params: readonly [Record<string, unknown>]) => Promise<unknown>;
41
+ call: (rpc: string, params: readonly [object]) => Promise<unknown>;
42
42
  };
43
43
  };
44
44
  isOpen: () => boolean;
@@ -114,7 +114,7 @@ function createServerWsRpcClient(baseUrl: string): ServerWsRpcClient {
114
114
  });
115
115
  });
116
116
 
117
- async function call(rpc: string, params: Record<string, unknown> = {}): Promise<unknown> {
117
+ async function call(rpc: string, params: object = {}): Promise<unknown> {
118
118
  await ready;
119
119
  const id = uuidv4();
120
120
  //oxlint-disable-next-line promise/avoid-new
@@ -139,7 +139,7 @@ function createServerWsRpcClient(baseUrl: string): ServerWsRpcClient {
139
139
  function getConnection(): ReturnType<ServerWsRpcClient["getConnection"]> {
140
140
  return {
141
141
  getSession: () => ({
142
- call: async (rpc: string, [params]: readonly [Record<string, unknown>]) => {
142
+ call: async (rpc: string, [params]: readonly [object]) => {
143
143
  const result = await call(rpc, params);
144
144
  return result;
145
145
  },
@@ -6,19 +6,19 @@ import pTimeout from "p-timeout";
6
6
  import type { RequestHandlers } from "./types.js";
7
7
 
8
8
  interface RpcSession {
9
- readonly call: (rpc: string, params: readonly [Record<string, unknown>]) => Promise<unknown>;
9
+ readonly call: (rpc: string, params: readonly [object]) => Promise<unknown>;
10
10
  }
11
11
  interface RpcConnection {
12
12
  readonly getSession: () => RpcSession;
13
13
  }
14
14
  interface RpcClient {
15
- readonly call: (rpc: string, params: Record<string, unknown>) => Promise<unknown>;
15
+ readonly call: (rpc: string, params: object) => Promise<unknown>;
16
16
  readonly getConnection: () => RpcConnection;
17
17
  }
18
18
 
19
19
  interface CallClientOptions {
20
20
  rpc: string;
21
- params?: Record<string, unknown>;
21
+ params?: object;
22
22
  client: RpcClient;
23
23
  }
24
24
 
@@ -1,29 +1,29 @@
1
1
  // Third party imports
2
2
 
3
3
  // Local imports
4
- import type { JsonRpcSchema, RequestHandlersWithValidation } from "./types.js";
4
+ import type { JsonRpcSchema, RequestHandlersWithValidation, ResponseOf } from "./types.js";
5
5
  import { type RpcClient, callRaw } from "./call_raw.js";
6
6
  import { consola } from "consola";
7
7
  import { validateSchema } from "./validate_schema.js";
8
8
 
9
9
  const ERROR_400 = 400;
10
10
 
11
- interface CallSchemaOptions {
12
- schema: JsonRpcSchema;
13
- params?: Record<string, unknown>;
11
+ interface CallSchemaOptions<Schema extends JsonRpcSchema> {
12
+ schema: Schema;
13
+ params?: object;
14
14
  client: RpcClient;
15
15
  timeout?: number;
16
16
  }
17
17
 
18
- async function callSchema(
19
- { schema, params = {}, client, timeout }: CallSchemaOptions,
18
+ async function callSchema<Schema extends JsonRpcSchema>(
19
+ { schema, params = {}, client, timeout }: CallSchemaOptions<Schema>,
20
20
  {
21
21
  request_error_function,
22
22
  response_function,
23
23
  response_error_function,
24
24
  validation_error_function,
25
25
  }: RequestHandlersWithValidation = {},
26
- ): Promise<unknown> {
26
+ ): Promise<ResponseOf<Schema>> {
27
27
  const { valid, error: schema_error } = validateSchema(schema, params);
28
28
 
29
29
  if (!valid) {
@@ -49,7 +49,9 @@ async function callSchema(
49
49
  response_error_function,
50
50
  },
51
51
  );
52
- return result;
52
+ // The microservice validates its responses against the schema `response` it was generated from.
53
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
54
+ return result as ResponseOf<Schema>;
53
55
  }
54
56
 
55
57
  export { callSchema };
@@ -21,6 +21,11 @@ type ParamsOf<Schema> = Schema extends { readonly __params?: infer Params }
21
21
  ? Params
22
22
  : Record<string, unknown>;
23
23
 
24
+ // Any microservice's `microservice_version` schema, whatever the package that generated it.
25
+ type MicroserviceVersionSchema = JsonRpcSchema & {
26
+ readonly __response?: { microservice_version: string };
27
+ };
28
+
24
29
  interface RequestHandlers<Response = unknown> {
25
30
  readonly request_error_function?: (error: unknown) => void | Promise<void>;
26
31
  // 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.
@@ -34,6 +39,7 @@ interface RequestHandlersWithValidation extends RequestHandlers {
34
39
 
35
40
  export type {
36
41
  JsonRpcSchema,
42
+ MicroserviceVersionSchema,
37
43
  ParamsOf,
38
44
  RequestHandlers,
39
45
  RequestHandlersWithValidation,