tsondb 0.5.8 → 0.5.10

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 (49) hide show
  1. package/dist/src/bin/tsondb.d.ts +1 -9
  2. package/dist/src/bin/tsondb.js +7 -0
  3. package/dist/src/index.d.ts +1 -1
  4. package/dist/src/node/index.d.ts +2 -2
  5. package/dist/src/node/index.js +8 -2
  6. package/dist/src/node/renderers/jsonschema/index.d.ts +1 -1
  7. package/dist/src/node/renderers/ts/index.d.ts +1 -1
  8. package/dist/src/node/{Schema.d.ts → schema/Schema.d.ts} +2 -2
  9. package/dist/src/node/{Schema.js → schema/Schema.js} +10 -7
  10. package/dist/src/node/schema/declarations/EntityDecl.d.ts +31 -30
  11. package/dist/src/node/schema/declarations/EntityDecl.js +1 -0
  12. package/dist/src/node/server/api/declarations.js +3 -4
  13. package/dist/src/node/server/api/git.js +1 -2
  14. package/dist/src/node/server/api/instances.js +2 -3
  15. package/dist/src/node/server/index.d.ts +7 -2
  16. package/dist/src/node/server/index.js +10 -10
  17. package/dist/src/node/server/init.d.ts +1 -1
  18. package/dist/src/node/server/init.js +10 -0
  19. package/dist/src/node/utils/displayName.d.ts +3 -0
  20. package/dist/src/node/utils/displayName.js +19 -0
  21. package/dist/src/shared/config.d.ts +11 -0
  22. package/dist/src/{node/renderers/Output.d.ts → shared/output.d.ts} +1 -1
  23. package/dist/src/shared/output.js +1 -0
  24. package/dist/src/shared/utils/compare.js +6 -1
  25. package/dist/src/shared/utils/displayName.d.ts +1 -1
  26. package/dist/src/shared/utils/displayName.js +1 -1
  27. package/dist/src/shared/utils/instances.d.ts +3 -2
  28. package/dist/src/shared/utils/instances.js +3 -3
  29. package/dist/src/shared/utils/markdown.js +1 -1
  30. package/dist/src/web/components/Git.js +1 -1
  31. package/dist/src/web/components/typeInputs/ArrayTypeInput.js +4 -4
  32. package/dist/src/web/components/typeInputs/NestedEntityMapTypeInput.js +16 -11
  33. package/dist/src/web/components/typeInputs/ObjectTypeInput.js +3 -3
  34. package/dist/src/web/components/typeInputs/ReferenceIdentifierTypeInput.js +1 -1
  35. package/dist/src/web/components/typeInputs/StringTypeInput.js +7 -3
  36. package/dist/src/web/components/typeInputs/TypeInput.d.ts +2 -2
  37. package/dist/src/web/components/typeInputs/TypeInput.js +5 -1
  38. package/dist/src/web/hooks/useInstanceNamesByEntity.d.ts +1 -1
  39. package/dist/src/web/hooks/useInstanceNamesByEntity.js +1 -1
  40. package/dist/src/web/hooks/useSecondaryDeclarations.d.ts +1 -1
  41. package/dist/src/web/hooks/useSecondaryDeclarations.js +3 -3
  42. package/dist/src/web/routes/CreateInstance.js +26 -12
  43. package/dist/src/web/routes/Entity.js +27 -13
  44. package/dist/src/web/routes/Home.js +15 -1
  45. package/dist/src/web/routes/Instance.js +26 -12
  46. package/dist/src/web/routes/NotFound.js +4 -0
  47. package/package.json +6 -6
  48. package/public/css/styles.css +128 -34
  49. /package/dist/src/{node/renderers/Output.js → shared/config.js} +0 -0
@@ -5,9 +5,13 @@ import { ValidationErrors } from "./utils/ValidationErrors.js";
5
5
  export const StringTypeInput = ({ type, value, onChange }) => {
6
6
  const { minLength, maxLength, pattern, isMarkdown } = type;
7
7
  const errors = validateStringConstraints(type, value);
8
- return (_jsx("div", { class: "field field--string", children: isMarkdown ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "editor", children: [_jsx("textarea", { value: value, minLength: minLength, maxLength: maxLength, onInput: event => {
9
- onChange(event.currentTarget.value);
10
- }, "aria-invalid": errors.length > 0 }), _jsx(ValidationErrors, { errors: errors })] }), _jsx("div", { className: "preview", children: _jsx(Markdown, { string: value }) })] })) : (_jsxs("div", { className: "editor", children: [_jsx("input", { type: "text", value: value, minLength: minLength, maxLength: maxLength, pattern: pattern, onInput: event => {
8
+ return (_jsx("div", { class: "field field--string", children: isMarkdown ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "editor", children: [_jsx("div", { class: "textarea-grow-wrap", "data-value": value, children: _jsx("textarea", { value: value, minLength: minLength, maxLength: maxLength, onInput: event => {
9
+ onChange(event.currentTarget.value);
10
+ }, "aria-invalid": errors.length > 0 }) }), _jsx(ValidationErrors, { errors: errors })] }), _jsx("div", { className: "preview", children: _jsx(Markdown, { string: value }) })] })) : (_jsxs("div", { className: "editor", children: [_jsx("input", { type: "text", value: value, minLength: minLength, maxLength: maxLength, pattern: pattern === undefined
11
+ ? undefined
12
+ : pattern.startsWith("^(?:") && pattern.endsWith(")$")
13
+ ? pattern.slice(4, -2)
14
+ : `.*${pattern}.*`, onInput: event => {
11
15
  onChange(event.currentTarget.value);
12
16
  }, "aria-invalid": errors.length > 0 }), _jsx(ValidationErrors, { errors: errors })] })) }));
13
17
  };
@@ -9,5 +9,5 @@ type Props = {
9
9
  getDeclFromDeclName: GetDeclFromDeclName;
10
10
  onChange: (value: unknown) => void;
11
11
  };
12
- export declare const TypeInput: FunctionComponent<Props>;
13
- export {};
12
+ declare const MemoizedTypeInput: FunctionComponent<Props>;
13
+ export { MemoizedTypeInput as TypeInput };
@@ -1,4 +1,6 @@
1
1
  import { jsx as _jsx } from "preact/jsx-runtime";
2
+ import { memo } from "preact/compat";
3
+ import { deepEqual } from "../../../shared/utils/compare.js";
2
4
  import { assertExhaustive } from "../../../shared/utils/typeSafety.js";
3
5
  import { ArrayTypeInput } from "./ArrayTypeInput.js";
4
6
  import { BooleanTypeInput } from "./BooleanTypeInput.js";
@@ -13,7 +15,7 @@ import { ObjectTypeInput } from "./ObjectTypeInput.js";
13
15
  import { ReferenceIdentifierTypeInput } from "./ReferenceIdentifierTypeInput.js";
14
16
  import { StringTypeInput } from "./StringTypeInput.js";
15
17
  import { MismatchingTypeError } from "./utils/MismatchingTypeError.js";
16
- export const TypeInput = ({ type, value, instanceNamesByEntity, getDeclFromDeclName, onChange, }) => {
18
+ const TypeInput = ({ type, value, instanceNamesByEntity, getDeclFromDeclName, onChange, }) => {
17
19
  switch (type.kind) {
18
20
  case "BooleanType":
19
21
  if (typeof value === "boolean") {
@@ -88,3 +90,5 @@ export const TypeInput = ({ type, value, instanceNamesByEntity, getDeclFromDeclN
88
90
  return assertExhaustive(type);
89
91
  }
90
92
  };
93
+ const MemoizedTypeInput = memo(TypeInput, (prevProps, nextProps) => deepEqual(prevProps.value, nextProps.value));
94
+ export { MemoizedTypeInput as TypeInput };
@@ -1,3 +1,3 @@
1
1
  import type { GetAllInstancesResponseBody } from "../../shared/api.ts";
2
2
  export type InstanceNamesByEntity = GetAllInstancesResponseBody["instances"];
3
- export declare const useInstanceNamesByEntity: (locales?: string[]) => [InstanceNamesByEntity, () => void];
3
+ export declare const useInstanceNamesByEntity: (locales?: string[]) => [InstanceNamesByEntity | undefined, () => void];
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useState } from "preact/hooks";
2
2
  import { getAllInstances } from "../api.js";
3
3
  export const useInstanceNamesByEntity = (locales = []) => {
4
- const [instanceNamesByEntity, setInstanceNamesByEntity] = useState({});
4
+ const [instanceNamesByEntity, setInstanceNamesByEntity] = useState();
5
5
  const updateInstanceNamesByEntity = useCallback(() => {
6
6
  getAllInstances(locales)
7
7
  .then(data => {
@@ -1,3 +1,3 @@
1
1
  import type { SerializedSecondaryDecl } from "../../node/schema/declarations/Declaration.ts";
2
2
  export type GetDeclFromDeclName = (name: string) => SerializedSecondaryDecl | undefined;
3
- export declare const useGetDeclFromDeclName: () => GetDeclFromDeclName;
3
+ export declare const useGetDeclFromDeclName: () => [GetDeclFromDeclName, loaded: boolean];
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useState } from "preact/hooks";
2
2
  import { getAllDeclarations } from "../api.js";
3
3
  export const useGetDeclFromDeclName = () => {
4
- const [secondaryDeclarations, setSecondaryDeclarations] = useState([]);
4
+ const [secondaryDeclarations, setSecondaryDeclarations] = useState();
5
5
  useEffect(() => {
6
6
  getAllDeclarations()
7
7
  .then(data => {
@@ -15,6 +15,6 @@ export const useGetDeclFromDeclName = () => {
15
15
  }
16
16
  });
17
17
  }, []);
18
- const getDeclFromDeclName = useCallback((name) => secondaryDeclarations.find(decl => decl.name === name), [secondaryDeclarations]);
19
- return getDeclFromDeclName;
18
+ const getDeclFromDeclName = useCallback((name) => secondaryDeclarations?.find(decl => decl.name === name), [secondaryDeclarations]);
19
+ return [getDeclFromDeclName, secondaryDeclarations !== undefined];
20
20
  };
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useLocation, useRoute } from "preact-iso";
3
3
  import { useEffect, useState } from "preact/hooks";
4
- import { getDisplayNameFromEntityInstance } from "../../shared/utils/displayName.js";
4
+ import { getSerializedDisplayNameFromEntityInstance } from "../../shared/utils/displayName.js";
5
5
  import { toTitleCase } from "../../shared/utils/string.js";
6
6
  import { validateLocaleIdentifier } from "../../shared/validation/identifier.js";
7
7
  import { createInstanceByEntityNameAndId } from "../api.js";
@@ -15,7 +15,7 @@ import { createTypeSkeleton } from "../utils/typeSkeleton.js";
15
15
  import { NotFound } from "./NotFound.js";
16
16
  export const CreateInstance = () => {
17
17
  const { params: { name }, } = useRoute();
18
- const getDeclFromDeclName = useGetDeclFromDeclName();
18
+ const [getDeclFromDeclName, declsLoaded] = useGetDeclFromDeclName();
19
19
  const entityFromRoute = useEntityFromRoute();
20
20
  const [instanceNamesByEntity] = useInstanceNamesByEntity();
21
21
  const [instance, setInstance] = useState();
@@ -26,10 +26,15 @@ export const CreateInstance = () => {
26
26
  }
27
27
  }, [getDeclFromDeclName, entityFromRoute]);
28
28
  const { route } = useLocation();
29
+ useEffect(() => {
30
+ const entityName = entityFromRoute?.entity.name ?? name;
31
+ document.title =
32
+ entityName === undefined ? "Not found" : "New " + toTitleCase(entityName) + " — TSONDB";
33
+ }, [entityFromRoute?.entity.name, name]);
29
34
  if (!name) {
30
35
  return _jsx(NotFound, {});
31
36
  }
32
- if (!entityFromRoute) {
37
+ if (!entityFromRoute || !instanceNamesByEntity || !declsLoaded) {
33
38
  return (_jsxs("div", { children: [_jsx("h1", { children: name }), _jsx("p", { className: "loading", children: "Loading \u2026" })] }));
34
39
  }
35
40
  const { entity, isLocaleEntity } = entityFromRoute;
@@ -39,13 +44,22 @@ export const CreateInstance = () => {
39
44
  if (name) {
40
45
  createInstanceByEntityNameAndId(entity.name, instance, isLocaleEntity ? customId : undefined)
41
46
  .then(createdInstance => {
42
- if (name === "saveandaddanother") {
43
- setInstance(createTypeSkeleton(getDeclFromDeclName, entity.type));
44
- setCustomId("");
45
- alert(`Instance of entity ${entity.name} created successfully with identifier ${createdInstance.instance.id}. You can add another instance now.`);
46
- }
47
- else {
48
- route(`/entities/${entity.name}?created=${encodeURIComponent(createdInstance.instance.id)}`);
47
+ switch (name) {
48
+ case "saveandcontinue": {
49
+ route(`/entities/${entity.name}/instances/${createdInstance.instance.id}`);
50
+ break;
51
+ }
52
+ case "saveandaddanother": {
53
+ setInstance(createTypeSkeleton(getDeclFromDeclName, entity.type));
54
+ setCustomId("");
55
+ alert(`Instance of entity ${entity.name} created successfully with identifier ${createdInstance.instance.id}. You can add another instance now.`);
56
+ break;
57
+ }
58
+ case "save":
59
+ default: {
60
+ route(`/entities/${entity.name}?created=${encodeURIComponent(createdInstance.instance.id)}`);
61
+ break;
62
+ }
49
63
  }
50
64
  })
51
65
  .catch((error) => {
@@ -56,7 +70,7 @@ export const CreateInstance = () => {
56
70
  }
57
71
  };
58
72
  const defaultName = customId || `New ${toTitleCase(entity.name)}`;
59
- const instanceName = getDisplayNameFromEntityInstance(entity, instance, defaultName);
73
+ const instanceName = getSerializedDisplayNameFromEntityInstance(entity, instance, defaultName);
60
74
  const idErrors = isLocaleEntity ? validateLocaleIdentifier(customId) : [];
61
75
  return (_jsxs(Layout, { breadcrumbs: [
62
76
  { url: "/", label: "Home" },
@@ -66,5 +80,5 @@ export const CreateInstance = () => {
66
80
  }, "aria-invalid": idErrors.length > 0 }), _jsx(ValidationErrors, { errors: idErrors })] })), _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TypeInput, { type: entity.type, value: instance, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: value => {
67
81
  console.log("onChange", value);
68
82
  setInstance(value);
69
- } }), _jsxs("div", { className: "btns", children: [_jsx("button", { type: "submit", class: "primary", name: "save", children: "Save" }), _jsx("button", { type: "submit", class: "primary", name: "saveandaddanother", children: "Save and Add Another" })] })] })] }));
83
+ } }), _jsxs("div", { class: "form-footer btns", children: [_jsx("button", { type: "submit", class: "primary", name: "save", children: "Save" }), _jsx("button", { type: "submit", class: "primary", name: "saveandcontinue", children: "Save and Continue" }), _jsx("button", { type: "submit", class: "primary", name: "saveandaddanother", children: "Save and Add Another" })] })] })] }));
70
84
  };
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useRoute } from "preact-iso";
3
- import { useEffect } from "preact/hooks";
3
+ import { useEffect, useState } from "preact/hooks";
4
4
  import { getGitStatusForDisplay, getLabelForGitStatus } from "../../shared/utils/git.js";
5
+ import { toTitleCase } from "../../shared/utils/string.js";
5
6
  import { deleteInstanceByEntityNameAndId, getEntityByName, getInstancesByEntityName, } from "../api.js";
6
7
  import { Layout } from "../components/Layout.js";
7
8
  import { useAPIResource } from "../hooks/useAPIResource.js";
@@ -11,8 +12,12 @@ import { NotFound } from "./NotFound.js";
11
12
  const mapInstances = (data) => data.instances;
12
13
  export const Entity = () => {
13
14
  const { params: { name }, query: { created }, } = useRoute();
15
+ const [searchText, setSearchText] = useState("");
14
16
  const [entity] = useAPIResource(getEntityByName, name ?? "");
15
17
  const [instances, reloadInstances] = useMappedAPIResource(getInstancesByEntityName, mapInstances, name ?? "");
18
+ useEffect(() => {
19
+ document.title = toTitleCase(entity?.declaration.namePlural ?? name ?? "") + " — TSONDB";
20
+ }, [entity?.declaration.namePlural, name]);
16
21
  useEffect(() => {
17
22
  if (created) {
18
23
  const instanceElement = document.getElementById(`instance-${created}`);
@@ -25,20 +30,29 @@ export const Entity = () => {
25
30
  return _jsx(NotFound, {});
26
31
  }
27
32
  if (!entity || !instances) {
28
- return (_jsxs("div", { children: [_jsx("h1", { children: name }), _jsx("p", { className: "loading", children: "Loading \u2026" })] }));
33
+ return (_jsxs("div", { children: [_jsx("h1", { children: toTitleCase(entity?.declaration.namePlural ?? name) }), _jsx("p", { className: "loading", children: "Loading \u2026" })] }));
29
34
  }
30
- return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: "Home" }], children: [_jsxs("div", { class: "header-with-btns", children: [_jsx("h1", { children: name }), _jsx("a", { class: "btn btn--primary", href: `/entities/${entity.declaration.name}/instances/create`, children: "Add" })] }), entity.declaration.comment && (_jsx(Markdown, { class: "description", string: entity.declaration.comment })), _jsxs("p", { children: [instances.length, " instance", instances.length === 1 ? "" : "s"] }), _jsx("ul", { class: "instances", children: instances.map(instance => {
35
+ const lowerSearchText = searchText.toLowerCase();
36
+ const filteredInstances = searchText.length === 0
37
+ ? instances
38
+ : instances.filter(instance => instance.id.includes(searchText) ||
39
+ instance.displayName.toLowerCase().includes(lowerSearchText));
40
+ return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: "Home" }], children: [_jsxs("div", { class: "header-with-btns", children: [_jsx("h1", { children: toTitleCase(entity.declaration.namePlural) }), _jsx("a", { class: "btn btn--primary", href: `/entities/${entity.declaration.name}/instances/create`, children: "Add" })] }), entity.declaration.comment && (_jsx(Markdown, { class: "description", string: entity.declaration.comment })), _jsxs("div", { className: "list-header", children: [_jsxs("p", { class: "instance-count", children: [searchText === "" ? "" : `${filteredInstances.length.toString()} of `, instances.length, " instance", instances.length === 1 ? "" : "s"] }), _jsxs("form", { action: "", rel: "search", onSubmit: e => {
41
+ e.preventDefault();
42
+ }, children: [_jsx("label", { htmlFor: "instance-search", class: "visually-hidden", children: "Search" }), _jsx("input", { type: "text", id: "instance-search", value: searchText, onInput: event => {
43
+ setSearchText(event.currentTarget.value);
44
+ } })] })] }), _jsx("ul", { class: "entries entries--instances", children: filteredInstances.map(instance => {
31
45
  const gitStatusForDisplay = getGitStatusForDisplay(instance.gitStatus);
32
- return (_jsxs("li", { id: `instance-${instance.id}`, class: `instance-item ${created === instance.id ? "instance-item--created" : ""} ${gitStatusForDisplay === undefined ? "" : `git-status--${gitStatusForDisplay}`}`, children: [_jsx("h2", { children: instance.displayName }), _jsx("p", { "aria-hidden": true, class: "id", children: instance.id }), gitStatusForDisplay !== undefined && (_jsx("p", { class: `git-status git-status--${gitStatusForDisplay}`, title: getLabelForGitStatus(gitStatusForDisplay), children: gitStatusForDisplay })), _jsxs("div", { className: "btns", children: [_jsx("a", { href: `/entities/${entity.declaration.name}/instances/${instance.id}`, class: "btn", children: "Edit" }), _jsx("button", { class: "destructive", onClick: () => {
33
- if (confirm("Are you sure you want to delete this instance?")) {
34
- deleteInstanceByEntityNameAndId(entity.declaration.name, instance.id)
35
- .then(() => reloadInstances())
36
- .catch((error) => {
37
- if (error instanceof Error) {
38
- alert("Error deleting instance:\n\n" + error.toString());
46
+ return (_jsxs("li", { id: `instance-${instance.id}`, class: `entries-item ${created === instance.id ? "entries-item--created" : ""} ${gitStatusForDisplay === undefined ? "" : `git-status--${gitStatusForDisplay}`}`, children: [_jsx("h2", { class: "entries-item__title", children: instance.displayName }), _jsx("p", { "aria-hidden": true, class: "entries-item__subtitle entries-item__subtitle--id", children: instance.id }), _jsxs("div", { class: "entries-item__side", children: [gitStatusForDisplay !== undefined && (_jsx("p", { class: `git-status git-status--${gitStatusForDisplay}`, title: getLabelForGitStatus(gitStatusForDisplay), children: gitStatusForDisplay })), _jsxs("div", { class: "btns", children: [_jsx("a", { href: `/entities/${entity.declaration.name}/instances/${instance.id}`, class: "btn", children: "Edit" }), _jsx("button", { class: "destructive", onClick: () => {
47
+ if (confirm("Are you sure you want to delete this instance?")) {
48
+ deleteInstanceByEntityNameAndId(entity.declaration.name, instance.id)
49
+ .then(() => reloadInstances())
50
+ .catch((error) => {
51
+ if (error instanceof Error) {
52
+ alert("Error deleting instance:\n\n" + error.toString());
53
+ }
54
+ });
39
55
  }
40
- });
41
- }
42
- }, children: "Delete" })] })] }, instance.id));
56
+ }, children: "Delete" })] })] })] }, instance.id));
43
57
  }) })] }));
44
58
  };
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
+ import { useEffect, useState } from "preact/hooks";
2
3
  import { toTitleCase } from "../../shared/utils/string.js";
3
4
  import { getAllEntities } from "../api.js";
4
5
  import { Layout } from "../components/Layout.js";
@@ -7,5 +8,18 @@ import { Markdown } from "../utils/Markdown.js";
7
8
  const mapEntities = (data) => data.declarations.sort((a, b) => a.declaration.name.localeCompare(b.declaration.name));
8
9
  export const Home = () => {
9
10
  const [entities] = useMappedAPIResource(getAllEntities, mapEntities);
10
- return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: "Home" }], children: [_jsx("h1", { children: "Entities" }), _jsx("ul", { class: "entities", children: (entities ?? []).map(entity => (_jsxs("li", { class: "entity-item", children: [_jsxs("div", { className: "title", children: [_jsx("h2", { children: toTitleCase(entity.declaration.name) }), entity.declaration.comment && (_jsx(Markdown, { class: "description", string: entity.declaration.comment }))] }), _jsxs("p", { class: "meta", children: [entity.instanceCount, " instance", entity.instanceCount === 1 ? "" : "s"] }), _jsx("div", { className: "btns", children: _jsx("a", { href: `/entities/${entity.declaration.name}`, class: "btn", children: "View" }) })] }, entity.declaration.name))) })] }));
11
+ useEffect(() => {
12
+ document.title = "Entities — TSONDB";
13
+ }, []);
14
+ const [searchText, setSearchText] = useState("");
15
+ const lowerSearchText = searchText.toLowerCase().replaceAll(" ", "");
16
+ const filteredEntities = searchText.length === 0
17
+ ? entities
18
+ : entities?.filter(entity => entity.declaration.name.toLowerCase().includes(lowerSearchText) ||
19
+ entity.declaration.namePlural.toLowerCase().includes(lowerSearchText));
20
+ return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: "Home" }], children: [_jsx("h1", { children: "Entities" }), _jsxs("div", { className: "list-header", children: [_jsxs("p", { class: "instance-count", children: [searchText === "" ? "" : `${(filteredEntities?.length ?? 0).toString()} of `, entities?.length ?? 0, " entit", entities?.length === 1 ? "y" : "ies"] }), _jsxs("form", { action: "", rel: "search", onSubmit: e => {
21
+ e.preventDefault();
22
+ }, children: [_jsx("label", { htmlFor: "entity-search", class: "visually-hidden", children: "Search" }), _jsx("input", { type: "text", id: "entity-search", value: searchText, onInput: event => {
23
+ setSearchText(event.currentTarget.value);
24
+ } })] })] }), _jsx("ul", { class: "entries entries--entities", children: (filteredEntities ?? []).map(entity => (_jsxs("li", { class: "entries-item", children: [_jsxs("div", { class: "entries-item__title", children: [_jsx("h2", { children: toTitleCase(entity.declaration.namePlural) }), entity.declaration.comment && (_jsx(Markdown, { class: "description", string: entity.declaration.comment }))] }), _jsxs("p", { class: "entries-item__subtitle", children: [entity.instanceCount, " instance", entity.instanceCount === 1 ? "" : "s"] }), _jsx("div", { class: "entries-item__side", children: _jsx("div", { class: "btns", children: _jsx("a", { href: `/entities/${entity.declaration.name}`, class: "btn", children: "View" }) }) })] }, entity.declaration.name))) })] }));
11
25
  };
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useLocation, useRoute } from "preact-iso";
3
- import { useEffect, useMemo, useState } from "preact/hooks";
4
- import { deepEqual } from "../../shared/utils/compare.js";
5
- import { getDisplayNameFromEntityInstance } from "../../shared/utils/displayName.js";
3
+ import { useCallback, useEffect, useState } from "preact/hooks";
4
+ import { getSerializedDisplayNameFromEntityInstance } from "../../shared/utils/displayName.js";
5
+ import { toTitleCase } from "../../shared/utils/string.js";
6
6
  import { deleteInstanceByEntityNameAndId, getInstanceByEntityNameAndId, updateInstanceByEntityNameAndId, } from "../api.js";
7
7
  import { Layout } from "../components/Layout.js";
8
8
  import { TypeInput } from "../components/typeInputs/TypeInput.js";
@@ -12,13 +12,23 @@ import { useGetDeclFromDeclName } from "../hooks/useSecondaryDeclarations.js";
12
12
  import { NotFound } from "./NotFound.js";
13
13
  export const Instance = () => {
14
14
  const { params: { name, id }, } = useRoute();
15
- const getDeclFromDeclName = useGetDeclFromDeclName();
15
+ const [getDeclFromDeclName, declsLoaded] = useGetDeclFromDeclName();
16
16
  const entityFromRoute = useEntityFromRoute();
17
17
  const [instanceNamesByEntity] = useInstanceNamesByEntity();
18
18
  const [instance, setInstance] = useState();
19
19
  const [originalInstance, setOriginalInstance] = useState();
20
20
  const { route } = useLocation();
21
- const hasChanges = useMemo(() => !deepEqual(instance?.content, originalInstance?.content), [instance?.content, originalInstance?.content]);
21
+ useEffect(() => {
22
+ if (entityFromRoute && instance && id) {
23
+ const defaultName = id;
24
+ const instanceName = getSerializedDisplayNameFromEntityInstance(entityFromRoute.entity, instance.content, defaultName);
25
+ const entityName = entityFromRoute.entity.name;
26
+ document.title = instanceName + " — " + toTitleCase(entityName) + " — TSONDB";
27
+ }
28
+ else {
29
+ document.title = "Not found — TSONDB";
30
+ }
31
+ }, [entityFromRoute, id, instance]);
22
32
  useEffect(() => {
23
33
  if (name && id) {
24
34
  getInstanceByEntityNameAndId(name, id)
@@ -33,7 +43,7 @@ export const Instance = () => {
33
43
  }, [id, name]);
34
44
  const handleSubmit = (event) => {
35
45
  event.preventDefault();
36
- if (name && id && instance) {
46
+ if (event.submitter?.getAttribute("name") === "save" && name && id && instance) {
37
47
  updateInstanceByEntityNameAndId(name, id, instance.content)
38
48
  .then(updatedInstance => {
39
49
  setInstance(updatedInstance.instance);
@@ -46,17 +56,24 @@ export const Instance = () => {
46
56
  });
47
57
  }
48
58
  };
59
+ const handleOnChange = useCallback((value) => {
60
+ setInstance(container => container && { ...container, content: value });
61
+ }, []);
49
62
  if (!name || !id) {
50
63
  return _jsx(NotFound, {});
51
64
  }
52
- if (!entityFromRoute || !instance || !originalInstance) {
65
+ if (!entityFromRoute ||
66
+ !instance ||
67
+ !originalInstance ||
68
+ !instanceNamesByEntity ||
69
+ !declsLoaded) {
53
70
  return (_jsxs(Layout, { breadcrumbs: [
54
71
  { url: "/", label: "Home" },
55
72
  { url: `/entities/${name}`, label: name },
56
73
  ], children: [_jsx("h1", { children: id }), _jsx("p", { className: "loading", children: "Loading \u2026" })] }));
57
74
  }
58
75
  const defaultName = id;
59
- const instanceName = getDisplayNameFromEntityInstance(entityFromRoute.entity, instance.content, defaultName);
76
+ const instanceName = getSerializedDisplayNameFromEntityInstance(entityFromRoute.entity, instance.content, defaultName);
60
77
  return (_jsxs(Layout, { breadcrumbs: [
61
78
  { url: "/", label: "Home" },
62
79
  { url: `/entities/${name}`, label: entityFromRoute.entity.name },
@@ -72,8 +89,5 @@ export const Instance = () => {
72
89
  }
73
90
  });
74
91
  }
75
- }, children: "Delete" })] }), _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TypeInput, { type: entityFromRoute.entity.type, value: instance.content, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: value => {
76
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
77
- setInstance(container => ({ ...container, content: value }));
78
- } }), _jsx("button", { type: "submit", disabled: !hasChanges, class: "primary", children: "Save" })] })] }));
92
+ }, children: "Delete" })] }), _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TypeInput, { type: entityFromRoute.entity.type, value: instance.content, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: handleOnChange }), _jsx("div", { class: "form-footer btns", children: _jsx("button", { type: "submit", name: "save", class: "primary", children: "Save" }) })] })] }));
79
93
  };
@@ -1,5 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
+ import { useEffect } from "preact/hooks";
2
3
  import { Layout } from "../components/Layout.js";
3
4
  export const NotFound = () => {
5
+ useEffect(() => {
6
+ document.title = "Not found — TSONDB";
7
+ }, []);
4
8
  return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: "Home" }], children: [_jsx("h1", { children: "404 Not Found" }), _jsx("p", { children: "The page you are looking for does not exist." })] }));
5
9
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsondb",
3
- "version": "0.5.8",
3
+ "version": "0.5.10",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "Lukas Obermann",
@@ -17,7 +17,7 @@
17
17
  ".": "./dist/src/index.js",
18
18
  "./renderer/jsonschema": "./dist/src/node/renderers/jsonschema/index.js",
19
19
  "./renderer/ts": "./dist/src/node/renderers/ts/index.js",
20
- "./schema": "./dist/src/node/Schema.js",
20
+ "./schema": "./dist/src/node/schema/Schema.js",
21
21
  "./schema/def": "./dist/src/node/schema/index.js"
22
22
  },
23
23
  "scripts": {
@@ -31,18 +31,18 @@
31
31
  "release:sign": "commit-and-tag-version --sign --signoff"
32
32
  },
33
33
  "devDependencies": {
34
- "@eslint/js": "^9.34.0",
34
+ "@eslint/js": "^9.35.0",
35
35
  "@types/debug": "^4.1.12",
36
36
  "@types/express": "^5.0.3",
37
37
  "@types/node": "^24.3.1",
38
38
  "commit-and-tag-version": "^12.6.0",
39
- "eslint": "^9.34.0",
39
+ "eslint": "^9.35.0",
40
40
  "eslint-plugin-react": "^7.37.5",
41
41
  "eslint-plugin-react-hooks": "^5.2.0",
42
42
  "globals": "^16.3.0",
43
43
  "prettier": "^3.6.2",
44
44
  "typescript": "^5.9.2",
45
- "typescript-eslint": "^8.42.0"
45
+ "typescript-eslint": "^8.43.0"
46
46
  },
47
47
  "dependencies": {
48
48
  "debug": "^4.4.1",
@@ -51,7 +51,7 @@
51
51
  "preact-iso": "^2.10.0",
52
52
  "simple-cli-args": "^0.1.2",
53
53
  "simple-git": "^3.28.0",
54
- "uuid": "^11.1.0"
54
+ "uuid": "^13.0.0"
55
55
  },
56
56
  "repository": "github:elyukai/tsondb",
57
57
  "bugs": {