@ocelescope/plugin 0.5.0 → 0.7.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 (2) hide show
  1. package/dist/index.js +257 -133
  2. package/package.json +21 -26
package/dist/index.js CHANGED
@@ -1,188 +1,312 @@
1
1
  import { BarList, FileDropzone, FullScreenUpload, OcelSelect, defineModule, defineModuleRoute, env, getModuleRoute, useInvalidate } from "@ocelescope/core";
2
2
  import { CheckIcon, Columns2Icon, DatabaseIcon, DownloadIcon, EllipsisVertical, PuzzleIcon, Rows2Icon, Trash2Icon, UploadIcon } from "lucide-react";
3
3
  import { useRouter } from "next/router.js";
4
- import { ActionIcon, Anchor, Badge, Box, Breadcrumbs, Button, Card, Center, Container, Divider, Flex, Group, Loader, LoadingOverlay, Menu, Modal, MultiSelect, ScrollArea, Select, SimpleGrid, Splitter, Stack, Switch, Text, ThemeIcon, Title, Tooltip } from "@mantine/core";
4
+ import { ActionIcon, Anchor, Badge, Box, Breadcrumbs, Button, Card, Center, Container, Divider, Flex, Group, Input, Loader, LoadingOverlay, Menu, Modal, MultiSelect, ScrollArea, Select, SimpleGrid, Skeleton, Slider, Splitter, Stack, Switch, Text, ThemeIcon, Title, Tooltip, useComputedColorScheme } from "@mantine/core";
5
5
  import { useDeletePlugin, useDisableDiscoveryMethod, useDownloadPluginResults, useEnableDiscoveryMethod, useEventAttributes, useEventCounts, useEventIds, useGetComputedValues, useGetExtensionMeta, useGetPlugin, useGetPluginMethod, useGetResourceMeta, useListDiscoveryMethods, useObjectAttributes, useObjectCounts, useObjectIds, usePluginResult, usePlugins, useRunPlugin, useSavePluginResults, useUpload } from "@ocelescope/api-base";
6
- import { memo, useCallback, useEffect, useMemo, useState } from "react";
6
+ import { createContext, memo, useCallback, useContext, useEffect, useMemo, useState } from "react";
7
7
  import { ResourceSelect, Visualization } from "@ocelescope/resources";
8
8
  import { Controller, useForm, useWatch } from "react-hook-form";
9
- import RefParser from "@apidevtools/json-schema-ref-parser";
10
9
  import validator from "@rjsf/validator-ajv8";
11
- import { unflatten } from "flat";
12
- import traverse from "json-schema-traverse";
13
- import { keepPreviousData } from "@tanstack/react-query";
10
+ import { getDefaultRegistry } from "@rjsf/core";
11
+ import Editor from "@monaco-editor/react";
14
12
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
13
  import { useDebouncedValue } from "@mantine/hooks";
14
+ import { keepPreviousData } from "@tanstack/react-query";
16
15
  import dynamic from "next/dynamic.js";
17
16
  import Link from "next/link.js";
18
17
  import { generateColor } from "@marko19907/string-to-color";
19
- //#region src/components/Form/Fields/custom.tsx
20
- const useSelectOptions = ({ pluginId, methodName, provider, control }) => {
18
+ //#region src/components/Form/Fields/CodeField.tsx
19
+ const EDITOR_HEIGHT = 160;
20
+ const OPTIONS = {
21
+ minimap: { enabled: false },
22
+ quickSuggestions: false,
23
+ suggestOnTriggerCharacters: false,
24
+ wordBasedSuggestions: "off",
25
+ lineNumbers: "on",
26
+ scrollBeyondLastLine: false,
27
+ automaticLayout: true,
28
+ scrollbar: { alwaysConsumeMouseWheel: false },
29
+ wordWrap: "on",
30
+ tabSize: 2,
31
+ fontSize: 13,
32
+ padding: {
33
+ top: 8,
34
+ bottom: 8
35
+ }
36
+ };
37
+ const CodeField = memo(({ schema, required, formData, onChange, rawErrors, disabled, readonly, fieldPathId: { path } }) => {
38
+ const meta = schema["x-ui-meta"];
39
+ const colorScheme = useComputedColorScheme("light", { getInitialValueInEffect: true });
40
+ const readOnly = disabled || readonly;
41
+ const options = useMemo(() => ({
42
+ ...OPTIONS,
43
+ readOnly
44
+ }), [readOnly]);
45
+ return /* @__PURE__ */ jsx(Input.Wrapper, {
46
+ label: schema.title,
47
+ description: schema.description,
48
+ required,
49
+ error: rawErrors?.[0],
50
+ children: /* @__PURE__ */ jsx(Box, {
51
+ style: {
52
+ border: "1px solid var(--mantine-color-default-border)",
53
+ borderRadius: "var(--mantine-radius-default)",
54
+ overflow: "hidden"
55
+ },
56
+ children: /* @__PURE__ */ jsx(Editor, {
57
+ height: EDITOR_HEIGHT,
58
+ language: meta?.language ?? "plaintext",
59
+ value: formData ?? "",
60
+ theme: colorScheme === "dark" ? "vs-dark" : "light",
61
+ options,
62
+ onChange: (value) => onChange(value ?? "", path),
63
+ loading: /* @__PURE__ */ jsx(Skeleton, {
64
+ height: EDITOR_HEIGHT,
65
+ radius: 0
66
+ })
67
+ })
68
+ })
69
+ });
70
+ });
71
+ //#endregion
72
+ //#region src/components/Form/PluginFormContext.tsx
73
+ const PluginFormContext = createContext(void 0);
74
+ const usePluginForm = () => {
75
+ const context = useContext(PluginFormContext);
76
+ if (!context) throw new Error("Custom plugin fields have to be rendered inside <PluginFormProvider>");
77
+ return context;
78
+ };
79
+ const useOcelId = (ocelRef) => {
80
+ const { control } = usePluginForm();
81
+ return useWatch({
82
+ control,
83
+ name: `input_ocels.${ocelRef}`
84
+ });
85
+ };
86
+ const PluginFormProvider = ({ pluginId, methodName, control, children }) => {
87
+ const value = useMemo(() => ({
88
+ pluginId,
89
+ methodName,
90
+ control
91
+ }), [
92
+ pluginId,
93
+ methodName,
94
+ control
95
+ ]);
96
+ return /* @__PURE__ */ jsx(PluginFormContext.Provider, {
97
+ value,
98
+ children
99
+ });
100
+ };
101
+ //#endregion
102
+ //#region src/components/Form/Fields/ComputedSelect.tsx
103
+ const compact = (record) => Object.fromEntries(Object.entries(record ?? {}).filter(([, value]) => !!value));
104
+ const useSelectOptions = (provider) => {
105
+ const { pluginId, methodName, control } = usePluginForm();
21
106
  const formValues = useWatch({ control });
22
- const { data } = useGetComputedValues(pluginId, methodName, provider, {
107
+ const [debouncedBody] = useDebouncedValue(useMemo(() => JSON.stringify({
23
108
  input: formValues.input,
24
- input_ocels: Object.fromEntries(Object.entries(formValues.input_ocels ?? {}).filter(([_, v]) => !!v && v.length >= 0)),
25
- input_resources: Object.fromEntries(Object.entries(formValues.input_resources ?? {}).filter(([_, v]) => !!v && v.length >= 0))
26
- }, { query: {
109
+ input_ocels: compact(formValues.input_ocels),
110
+ input_resources: compact(formValues.input_resources)
111
+ }), [formValues]), 300);
112
+ const { data } = useGetComputedValues(pluginId, methodName, provider, useMemo(() => JSON.parse(debouncedBody), [debouncedBody]), { query: {
27
113
  placeholderData: keepPreviousData,
28
114
  refetchOnWindowFocus: false,
29
115
  refetchOnReconnect: false
30
116
  } });
31
117
  return { options: data };
32
118
  };
33
- const getComputedSelect = ({ pluginId, methodName, control }) => ({ computed_select: memo(({ schema, required, formData, onChange, fieldPathId: { path } }) => {
34
- const meta = schema?.["x-ui-meta"];
35
- const SelectComponent = schema.type === "array" ? MultiSelect : Select;
36
- const { options } = useSelectOptions({
37
- pluginId,
38
- methodName,
39
- control,
40
- provider: meta.provider
41
- });
119
+ const ComputedSelect = memo(({ schema, required, formData, onChange, fieldPathId: { path } }) => {
120
+ const meta = schema["x-ui-meta"];
121
+ const isMulti = schema.type === "array";
122
+ const SelectComponent = isMulti ? MultiSelect : Select;
123
+ const { options } = useSelectOptions(meta.provider);
42
124
  return /* @__PURE__ */ jsx(SelectComponent, {
43
125
  label: schema.title,
44
126
  description: schema.description,
45
127
  required,
46
- value: formData ?? (schema.type === "array" ? [] : null),
128
+ value: formData ?? (isMulti ? [] : null),
47
129
  onChange: (value) => onChange(value, path),
48
130
  data: options
49
131
  });
50
- }) });
132
+ });
51
133
  //#endregion
52
- //#region src/components/Form/Fields/ocel.tsx
53
- const AttributeSelector = (query) => ({ isMulti, ocelId, onChange, value, label, required, description }) => {
54
- const { data: attributes = [] } = query(ocelId);
55
- const attributeNames = new Set(attributes.map(({ name }) => name));
56
- return /* @__PURE__ */ jsx(isMulti ? MultiSelect : Select, {
57
- value: value ?? (isMulti ? [] : null),
58
- label,
59
- onChange,
134
+ //#region src/components/Form/Fields/EnumMultiSelect.tsx
135
+ const itemsOf = (schema, registry) => {
136
+ const { items } = schema;
137
+ if (!items || typeof items === "boolean" || Array.isArray(items)) return;
138
+ return registry.schemaUtils.retrieveSchema(items);
139
+ };
140
+ const isStringEnumArray = (schema, registry) => {
141
+ if (schema.type !== "array") return false;
142
+ const enumValues = itemsOf(schema, registry)?.enum;
143
+ return Array.isArray(enumValues) && enumValues.length > 0 && enumValues.every((value) => typeof value === "string");
144
+ };
145
+ const EnumMultiSelect = memo(({ schema, registry, required, formData, onChange, rawErrors, disabled, readonly, fieldPathId: { path } }) => {
146
+ const options = useMemo(() => itemsOf(schema, registry)?.enum ?? [], [schema, registry]);
147
+ return /* @__PURE__ */ jsx(MultiSelect, {
148
+ label: schema.title,
149
+ description: schema.description,
60
150
  required,
61
- description,
151
+ error: rawErrors?.[0],
152
+ data: options,
153
+ value: Array.isArray(formData) ? formData : [],
154
+ onChange: (value) => onChange(value, path),
155
+ disabled: disabled || readonly,
62
156
  clearable: true,
63
- data: [...attributeNames]
157
+ searchable: true
158
+ });
159
+ });
160
+ //#endregion
161
+ //#region src/components/Form/Fields/OCELField.tsx
162
+ const AttributeSelect = (useAttributes) => ({ ocelId, isMulti, value, ...props }) => {
163
+ const { data: attributes } = useAttributes(ocelId);
164
+ const names = useMemo(() => [...new Set((attributes ?? []).map(({ name }) => name))], [attributes]);
165
+ return /* @__PURE__ */ jsx(isMulti ? MultiSelect : Select, {
166
+ value: value ?? (isMulti ? [] : null),
167
+ ...props,
168
+ data: names,
169
+ disabled: !ocelId,
170
+ clearable: true
64
171
  });
65
172
  };
66
- const TypeSelector = (query) => ({ ocelId, onChange, required, value, isMulti, label, description }) => {
67
- const { data = {} } = query(ocelId);
173
+ const TypeSelect = (useCounts) => ({ ocelId, isMulti, value, ...props }) => {
174
+ const { data: counts } = useCounts(ocelId);
175
+ const types = useMemo(() => Object.keys(counts ?? {}), [counts]);
68
176
  return /* @__PURE__ */ jsx(isMulti ? MultiSelect : Select, {
177
+ ...props,
69
178
  value: value ?? (isMulti ? [] : null),
70
- label,
71
- required,
72
- clearable: true,
73
- description,
74
- onChange,
75
- data: Object.keys(data)
179
+ data: types,
180
+ clearable: true
76
181
  });
77
182
  };
78
- const IdSelect = (query) => ({ ocelId, isMulti, value, ...rest }) => {
79
- const [searchValue, setSearchValue] = useState();
80
- const [debouncedSearch] = useDebouncedValue(searchValue, 300);
81
- const { data: ids } = query(ocelId, { search: debouncedSearch });
183
+ const IdSelect = (useIds) => ({ ocelId, isMulti, value, ...props }) => {
184
+ const [search, setSearch] = useState();
185
+ const [debouncedSearch] = useDebouncedValue(search, 300);
186
+ const { data: ids } = useIds(ocelId, { search: debouncedSearch });
82
187
  return /* @__PURE__ */ jsx(isMulti ? MultiSelect : Select, {
83
- searchable: true,
84
- searchValue,
85
- onSearchChange: (newSearchValue) => setSearchValue(newSearchValue),
86
- data: ids?.response,
188
+ ...props,
87
189
  value: value ?? (isMulti ? [] : null),
88
- ...rest
190
+ data: ids?.response,
191
+ searchValue: search,
192
+ onSearchChange: setSearch,
193
+ searchable: true
89
194
  });
90
195
  };
91
- const ocelFieldMap = {
92
- event_type: TypeSelector(useEventCounts),
93
- object_type: TypeSelector(useObjectCounts),
94
- event_attribute: AttributeSelector(useEventAttributes),
95
- object_attribute: AttributeSelector(useObjectAttributes),
196
+ const OCEL_FIELDS = {
197
+ event_type: TypeSelect(useEventCounts),
198
+ object_type: TypeSelect(useObjectCounts),
199
+ event_attribute: AttributeSelect(useEventAttributes),
200
+ object_attribute: AttributeSelect(useObjectAttributes),
96
201
  event_id: IdSelect(useEventIds),
97
202
  object_id: IdSelect(useObjectIds)
98
203
  };
99
- const wrapFieldsWithContext = (control) => {
100
- const wrapped = {};
101
- for (const [name, Field] of Object.entries(ocelFieldMap)) {
102
- const Comp = ({ schema, required, formData, fieldPathId: { path }, onChange }) => {
103
- const ocelRef = schema?.["x-ui-meta"]?.ocel_id;
104
- const isMulti = schema?.type === "array";
105
- const ocelId = useWatch({
106
- control,
107
- name: `input_ocels.${ocelRef}`
108
- });
109
- return /* @__PURE__ */ jsx(Field, {
110
- label: schema?.title,
111
- required,
112
- description: schema?.description,
113
- isMulti,
114
- onChange: (data) => onChange(data, path),
115
- value: formData,
116
- ocelId
117
- });
118
- };
119
- wrapped[name] = memo(Comp);
120
- }
121
- return wrapped;
204
+ const OCELField = memo(({ schema, required, formData, onChange, fieldPathId: { path } }) => {
205
+ const ocelRef = schema["x-ui-meta"]?.["ocel_id"];
206
+ const ocelFieldType = schema["x-ui-meta"]?.["field_type"];
207
+ const ocelId = useOcelId(ocelRef);
208
+ const isMulti = schema.type === "array";
209
+ useEffect(() => {
210
+ onChange(isMulti ? [] : void 0, path);
211
+ }, [
212
+ ocelId,
213
+ isMulti,
214
+ onChange,
215
+ path
216
+ ]);
217
+ const Selector = OCEL_FIELDS[ocelFieldType];
218
+ return Selector && /* @__PURE__ */ jsx(Selector, {
219
+ ocelId,
220
+ isMulti,
221
+ value: formData,
222
+ onChange: (value) => onChange(value, path),
223
+ label: schema.title,
224
+ description: schema.description,
225
+ disabled: !ocelId,
226
+ required
227
+ }, ocelId);
228
+ });
229
+ //#endregion
230
+ //#region src/components/Form/Fields/SliderField.tsx
231
+ const decimalsOf = (step) => String(step).split(".")[1]?.length ?? 0;
232
+ const SliderField = memo(({ schema, required, formData, onChange, rawErrors, disabled, readonly, fieldPathId: { path } }) => {
233
+ const meta = schema["x-ui-meta"];
234
+ const { min, max } = meta;
235
+ const step = meta.step ?? (schema.type === "integer" ? 1 : (max - min) / 100);
236
+ const decimals = decimalsOf(step);
237
+ const marks = useMemo(() => meta.marks?.map((value) => ({
238
+ value,
239
+ label: value.toFixed(decimals)
240
+ })), [meta.marks, decimals]);
241
+ const fallback = typeof schema.default === "number" ? schema.default : min;
242
+ const value = typeof formData === "number" ? formData : fallback;
243
+ return /* @__PURE__ */ jsx(Input.Wrapper, {
244
+ label: schema.title,
245
+ description: schema.description,
246
+ required,
247
+ error: rawErrors?.[0],
248
+ children: /* @__PURE__ */ jsx(Box, {
249
+ px: marks ? "md" : void 0,
250
+ pb: marks ? "xl" : void 0,
251
+ children: /* @__PURE__ */ jsx(Slider, {
252
+ mt: "xs",
253
+ min,
254
+ max,
255
+ step,
256
+ marks,
257
+ label: (current) => current.toFixed(decimals),
258
+ value,
259
+ onChange: (current) => onChange(current, path),
260
+ disabled: disabled || readonly
261
+ })
262
+ })
263
+ });
264
+ });
265
+ //#endregion
266
+ //#region src/components/Form/Fields/index.tsx
267
+ const DefaultSchemaField = getDefaultRegistry().fields.SchemaField;
268
+ const CUSTOM_FORM_MAP = {
269
+ ocel: OCELField,
270
+ computed_select: ComputedSelect,
271
+ code: CodeField,
272
+ slider: SliderField
273
+ };
274
+ const resolveField = ({ schema, registry }) => {
275
+ const uiType = schema["x-ui-meta"]?.type;
276
+ if (uiType) return CUSTOM_FORM_MAP[uiType];
277
+ if (isStringEnumArray(schema, registry)) return EnumMultiSelect;
278
+ };
279
+ const CustomSchemaField = (props) => {
280
+ const FormComponent = resolveField(props);
281
+ return FormComponent ? /* @__PURE__ */ jsx(FormComponent, { ...props }) : /* @__PURE__ */ jsx(DefaultSchemaField, { ...props });
122
282
  };
123
283
  //#endregion
124
284
  //#region src/components/Form/MantineForm.tsx
125
285
  const Form = dynamic(() => import("@rjsf/mantine").then((m) => m.Form), { ssr: false });
126
286
  //#endregion
127
287
  //#region src/components/Form/PluginForm.tsx
128
- const buildUiSchemaV2 = async ({ schema }) => {
129
- const deref = await RefParser.dereference(schema, { dereference: { circular: "ignore" } });
130
- const uiSchema = {};
131
- traverse(deref, {
132
- allKeys: true,
133
- cb: (subschema, pointer) => {
134
- const pointerComponents = pointer.split("/").slice(1);
135
- if (pointerComponents[0] !== "properties") return;
136
- if (pointerComponents[0] === "properties" && pointerComponents.at(-1) === "x-ui-meta") {
137
- const path = pointerComponents.filter((pathComponent) => !["x-ui-meta", "properties"].includes(pathComponent)).join(".");
138
- uiSchema[path] = { "ui:field": subschema.field_type ?? subschema.type };
139
- }
140
- }
141
- });
142
- return unflatten(uiSchema);
143
- };
288
+ const FIELDS = { SchemaField: CustomSchemaField };
144
289
  const PluginForm = ({ schema, control, pluginId, methodName, onSubmit }) => {
145
- const [uiSchema, setUiSchema] = useState(void 0);
146
- useEffect(() => {
147
- let cancelled = false;
148
- (async () => {
149
- const ui = await buildUiSchemaV2({ schema });
150
- if (!cancelled) setUiSchema(ui);
151
- })();
152
- return () => {
153
- cancelled = true;
154
- };
155
- }, [schema]);
156
- const fields = useMemo(() => wrapFieldsWithContext(control), [control]);
157
- const computedFields = useMemo(() => getComputedSelect({
158
- methodName,
290
+ const formSchema = useMemo(() => ({
291
+ ...schema,
292
+ title: ""
293
+ }), [schema]);
294
+ return /* @__PURE__ */ jsx(PluginFormProvider, {
295
+ control,
159
296
  pluginId,
160
- control
161
- }), [
162
297
  methodName,
163
- pluginId,
164
- control
165
- ]);
166
- return /* @__PURE__ */ jsx(Controller, {
167
- control,
168
- name: "input",
169
- render: ({ field }) => /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Form, {
170
- schema: {
171
- ...schema,
172
- title: ""
173
- },
174
- formData: field.value,
175
- validator,
176
- uiSchema,
177
- fields: {
178
- ...fields,
179
- ...computedFields
180
- },
181
- onChange: (data) => {
182
- field.onChange(data.formData);
183
- },
184
- onSubmit
185
- }) })
298
+ children: /* @__PURE__ */ jsx(Controller, {
299
+ control,
300
+ name: "input",
301
+ render: ({ field }) => /* @__PURE__ */ jsx(Form, {
302
+ schema: formSchema,
303
+ formData: field.value,
304
+ validator,
305
+ fields: FIELDS,
306
+ onChange: ({ formData }) => field.onChange(formData),
307
+ onSubmit
308
+ })
309
+ })
186
310
  });
187
311
  };
188
312
  //#endregion
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ocelescope/plugin",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.7.0",
5
5
  "description": "A module for inspections of OCELs",
6
6
  "author": "Görkem-Emre Öztürk <goerkem.oeztuerk@rwth-aachen.de>",
7
7
  "license": "MIT",
@@ -27,40 +27,35 @@
27
27
  "access": "public"
28
28
  },
29
29
  "peerDependencies": {
30
- "@apidevtools/json-schema-ref-parser": "^15.5.0",
31
- "@mantine/carousel": "^9.4.2",
32
- "@mantine/core": "^9.4.2",
33
- "@mantine/hooks": "^9.4.2",
30
+ "@mantine/carousel": "^9.5.1",
31
+ "@mantine/core": "^9.5.1",
32
+ "@mantine/hooks": "^9.5.1",
34
33
  "@marko19907/string-to-color": "^1.0.2",
35
- "@rjsf/core": "^6.7.0",
36
- "@rjsf/mantine": "^6.7.0",
37
- "@rjsf/utils": "^6.7.0",
38
- "@rjsf/validator-ajv8": "^6.7.0",
34
+ "@monaco-editor/react": "^4.7.0",
35
+ "@rjsf/core": "^6.8.0",
36
+ "@rjsf/mantine": "^6.8.0",
37
+ "@rjsf/utils": "^6.8.0",
38
+ "@rjsf/validator-ajv8": "^6.8.0",
39
39
  "@tanstack/react-query": "^5.90.21",
40
40
  "embla-carousel": "^8.6.0",
41
41
  "embla-carousel-react": "^8.6.0",
42
- "flat": "^6.0.1",
43
- "json-schema-traverse": "^1.0.0",
44
42
  "lucide-react": "^1.0.0",
45
- "next": "^16.2.10",
46
- "react": "^19.2.7",
47
- "react-dom": "^19.2.7",
43
+ "next": "16.3.0",
44
+ "react": "^19.2.8",
45
+ "react-dom": "^19.2.8",
48
46
  "react-hook-form": "^7.71.2",
49
- "@ocelescope/core": "^0.5.0",
50
- "@ocelescope/resources": "^0.5.0",
51
- "@ocelescope/api-base": "^0.5.0"
47
+ "@ocelescope/core": "^0.7.0",
48
+ "@ocelescope/api-base": "^0.7.0",
49
+ "@ocelescope/resources": "^0.7.0"
52
50
  },
53
51
  "devDependencies": {
54
- "@apidevtools/json-schema-ref-parser": "^15.5.0",
55
- "@rjsf/mantine": "^6.7.0",
52
+ "@rjsf/mantine": "^6.8.0",
56
53
  "@types/file-saver": "^2.0.7",
57
- "@types/node": "^26.1.1",
58
- "@types/react": "^19.2.17",
59
- "@types/react-dom": "^19.2.3",
60
- "flat": "^6.0.1",
61
- "json-schema-traverse": "^1.0.0",
62
- "tsdown": "^0.22.12",
63
- "typescript": "^5.9.3"
54
+ "@types/node": "^26.2.0",
55
+ "@types/react": "^19.2.18",
56
+ "@types/react-dom": "^19.2.4",
57
+ "tsdown": "^0.22.14",
58
+ "typescript": "^6.0.3"
64
59
  },
65
60
  "scripts": {
66
61
  "build": "tsdown",