diff --git a/.gitignore b/.gitignore index 15b7372..6965d57 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ dist-ssr # code-generated files packages/lib/src/clients/backend apps/admin/src/clients/admin +.oxlint-plugins diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..5ff8e41 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,268 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": [ + "unicorn", + "typescript", + "react" + ], + "options": { + "typeAware": true + }, + "categories": { + "correctness": "off" + }, + "env": { + "builtin": true, + "browser": true + }, + "ignorePatterns": [ + "**/dist/**", + "packages/lib/src/clients/**", + "apps/admin/src/clients/admin/**", + ], + "jsPlugins": [ + { + "name": "anti-slop", + "specifier": "./.oxlint-plugins/anti-slop/index.ts" + } + ], + "rules": { + "no-unexpected-multiline": "error", + "constructor-super": "error", + "for-direction": "error", + "getter-return": "error", + "no-async-promise-executor": "error", + "no-case-declarations": "error", + "no-class-assign": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": "error", + "no-const-assign": "error", + "no-constant-binary-expression": "error", + "no-constant-condition": "error", + "no-control-regex": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-dupe-class-members": "error", + "no-dupe-else-if": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-empty": "error", + "no-empty-character-class": "error", + "no-empty-pattern": "error", + "no-empty-static-block": "error", + "no-ex-assign": "error", + "no-extra-boolean-cast": "error", + "no-fallthrough": "error", + "no-func-assign": "error", + "no-global-assign": "error", + "no-import-assign": "error", + "no-invalid-regexp": "error", + "no-irregular-whitespace": "error", + "no-loss-of-precision": "error", + "no-misleading-character-class": "error", + "no-new-native-nonconstructor": "error", + "no-nonoctal-decimal-escape": "error", + "no-obj-calls": "error", + "no-prototype-builtins": "error", + "no-redeclare": "error", + "no-regex-spaces": "error", + "no-self-assign": "error", + "no-setter-return": "error", + "no-shadow-restricted-names": "error", + "no-sparse-arrays": "error", + "no-this-before-super": "error", + "no-unassigned-vars": "error", + "no-unreachable": "error", + "no-unsafe-finally": "error", + "no-unsafe-negation": "error", + "no-unsafe-optional-chaining": "error", + "no-unused-labels": "error", + "no-unused-private-class-members": "error", + "no-unused-vars": "error", + "no-useless-backreference": "error", + "no-useless-catch": "error", + "no-useless-escape": "error", + "no-with": "error", + "preserve-caught-error": "error", + "require-yield": "error", + "use-isnan": "error", + "valid-typeof": "error", + "no-array-constructor": "error", + "no-unused-expressions": "error", + "array-callback-return": "error", + "no-await-in-loop": "error", + "no-constructor-return": "error", + "no-inner-declarations": "error", + "no-promise-executor-return": "error", + "no-self-compare": "error", + "no-template-curly-in-string": "error", + "no-use-before-define": "error", + "arrow-body-style": "error", + "block-scoped-var": "error", + "default-case": "error", + "default-case-last": "error", + "default-param-last": "error", + "eqeqeq": "error", + "func-name-matching": "error", + "func-style": [ + "error", + "declaration" + ], + "init-declarations": [ + "error", + "always" + ], + "no-caller": "error", + "no-else-return": "error", + "no-label-var": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-return-assign": "error", + "no-sequences": "error", + "no-throw-literal": "error", + "no-unneeded-ternary": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "error", + "prefer-const": "error", + "require-await": "error", + "typescript/ban-ts-comment": "error", + "typescript/no-duplicate-enum-values": "error", + "typescript/no-empty-object-type": "error", + "typescript/no-explicit-any": "error", + "typescript/no-extra-non-null-assertion": "error", + "typescript/no-misused-new": "error", + "typescript/no-namespace": "error", + "typescript/no-non-null-asserted-optional-chain": "error", + "typescript/no-require-imports": "error", + "typescript/no-this-alias": "error", + "typescript/no-unnecessary-type-constraint": "error", + "typescript/no-unsafe-declaration-merging": "error", + "typescript/no-unsafe-function-type": "error", + "typescript/no-wrapper-object-types": "error", + "typescript/prefer-as-const": "error", + "typescript/prefer-namespace-keyword": "error", + "typescript/triple-slash-reference": "error", + "typescript/array-type": "error", + "typescript/await-thenable": "error", + "typescript/no-confusing-void-expression": [ + "error", + { + "ignoreArrowShorthand": true + } + ], + "typescript/no-floating-promises": "error", + "typescript/no-misused-promises": [ + "error", + { + "checksVoidReturn": { + "attributes": false + } + } + ], + "typescript/no-misused-spread": "error", + "typescript/no-unnecessary-condition": "error", + "typescript/no-unnecessary-type-assertion": "error", + "typescript/only-throw-error": "error", + "typescript/restrict-template-expressions": [ + "error", + { + "allowNumber": true, + "allowBoolean": true + } + ], + "typescript/switch-exhaustiveness-check": "error", + "no-undef": "error", + "no-useless-assignment": "error", + "no-unreachable-loop": "error", + "id-match": [ + "error", + "^_*(?:[A-Z][A-Z0-9_]*|[A-Za-z$][A-Za-z0-9$]*)_*$|^_+$", + { + "properties": false, + "classFields": false, + "ignoreDestructuring": false, + "onlyDeclarations": true + } + ], + "anti-slop/no-chained-type-assertions": "error", + "anti-slop/no-conditional-empty-object-spread": "error", + "anti-slop/no-known-value-widening": "error", + "anti-slop/no-module-mocking": "error", + "anti-slop/no-object-parameters": "error", + "anti-slop/no-reflect-apply": "error", + "anti-slop/no-reflect-get": "error", + "anti-slop/no-runtime-typeof": "error", + "anti-slop/no-shape-in-symbol-names": "error", + "anti-slop/no-unknown-parameters": "error", + "anti-slop/no-unknown-returns": "error", + "anti-slop/no-unknown-type-aliases": "error", + "anti-slop/no-unsafe-dictionary-type": "error", + "anti-slop/no-widen-then-assert": "error", + "anti-slop/require-safety-comment-for-type-assertion": "error", + "react/rules-of-hooks": "error", + "react/exhaustive-deps": "error", + "react/jsx-key": "error", + "react/jsx-no-duplicate-props": "error", + "react/no-unstable-nested-components": "error", + "react/jsx-no-target-blank": "error", + "react/void-dom-elements-no-children": "error", + "unicorn/error-message": "error", + "unicorn/no-abusive-eslint-disable": "error", + "unicorn/no-instanceof-builtins": "error", + "unicorn/no-invalid-remove-event-listener": "error", + "unicorn/no-thenable": "error", + "unicorn/no-useless-fallback-in-spread": "error", + "unicorn/no-useless-spread": "error", + "unicorn/prefer-node-protocol": "error", + "unicorn/throw-new-error": "error" + }, + "overrides": [ + { + "files": [ + "**/*.ts", + "**/*.tsx", + "**/*.mts", + "**/*.cts" + ], + "rules": { + "constructor-super": "off", + "getter-return": "off", + "no-class-assign": "off", + "no-const-assign": "off", + "no-dupe-class-members": "off", + "no-dupe-keys": "off", + "no-func-assign": "off", + "no-import-assign": "off", + "no-new-native-nonconstructor": "off", + "no-obj-calls": "off", + "no-redeclare": "off", + "no-setter-return": "off", + "no-this-before-super": "off", + "no-unreachable": "off", + "no-unsafe-negation": "off", + "no-with": "off", + "prefer-rest-params": "error", + "prefer-spread": "error" + } + }, + { + "files": [ + "configs/**" + ], + "globals": { + "window": "writable" + } + }, + { + "files": [ + "scripts/**", + "server.mjs" + ], + "env": { + "node": true + } + } + ] +} \ No newline at end of file diff --git a/apps/admin/src/components/AuthNavControl.tsx b/apps/admin/src/components/AuthNavControl.tsx index 67c2b42..8ef28a8 100644 --- a/apps/admin/src/components/AuthNavControl.tsx +++ b/apps/admin/src/components/AuthNavControl.tsx @@ -20,7 +20,7 @@ export function AuthNavControl(): ReactElement { }); } finally { clearAuthToken(); - navigate("/login"); + void navigate("/login"); setLoggingOut(false); } } diff --git a/apps/admin/src/components/ui/Badge.tsx b/apps/admin/src/components/ui/Badge.tsx index 67f4f62..2444b98 100644 --- a/apps/admin/src/components/ui/Badge.tsx +++ b/apps/admin/src/components/ui/Badge.tsx @@ -11,11 +11,11 @@ interface BadgeProps { type?: BadgeType; } -const typeClasses: Record = { +const typeClasses = { info: "bg-surface-2 border-2 border-border text-primary", success: "bg-success/20 border-2 border-success/60 text-primary", warning: "bg-warning/20 border-2 border-warning/60 text-primary", -}; +} satisfies Record; export function Badge({ children, diff --git a/apps/admin/src/components/ui/MultiSelectFilter.tsx b/apps/admin/src/components/ui/MultiSelectFilter.tsx index 172534c..e4d9fc4 100644 --- a/apps/admin/src/components/ui/MultiSelectFilter.tsx +++ b/apps/admin/src/components/ui/MultiSelectFilter.tsx @@ -31,6 +31,7 @@ export function MultiSelectFilter({ function handlePointerDown(event: MouseEvent): void { if ( containerRef.current && + // SAFETY: `contains` expects a DOM Node; event targets inside the document are Nodes. !containerRef.current.contains(event.target as Node) ) { setOpen(false); diff --git a/apps/admin/src/components/ui/ObjectSummary.tsx b/apps/admin/src/components/ui/ObjectSummary.tsx index 4b7e94c..fc18733 100644 --- a/apps/admin/src/components/ui/ObjectSummary.tsx +++ b/apps/admin/src/components/ui/ObjectSummary.tsx @@ -35,12 +35,12 @@ export function ObjectSummary({ name: ReactNode; layout?: "rows" | "columnar"; }): ReactElement { - const equatorial = catalogs?.coordinates?.equatorial; - const redshift = catalogs?.redshift; - const nature = catalogs?.nature; - const raUnit = schema.units.coordinates?.equatorial?.ra || "deg"; - const eRaUnit = schema.units.coordinates?.equatorial?.e_ra || raUnit; - const eDecUnit = schema.units.coordinates?.equatorial?.e_dec || raUnit; + const equatorial = catalogs.coordinates?.equatorial; + const redshift = catalogs.redshift; + const nature = catalogs.nature; + const raUnit = schema.units.coordinates.equatorial.ra || "deg"; + const eRaUnit = schema.units.coordinates.equatorial.e_ra || raUnit; + const eDecUnit = schema.units.coordinates.equatorial.e_dec || raUnit; const nameField = ( <> diff --git a/apps/admin/src/origins.ts b/apps/admin/src/origins.ts index 597189f..01bab60 100644 --- a/apps/admin/src/origins.ts +++ b/apps/admin/src/origins.ts @@ -1,19 +1,18 @@ import { sameEnvWebOrigin } from "@hyperleda/lib/origins"; -export function publicObjectUrl(pgc: number): { +interface ExternalLinkTarget { href: string; external: boolean; -} { +} + +export function publicObjectUrl(pgc: number): ExternalLinkTarget { return { href: `${sameEnvWebOrigin()}/object/${pgc}`, external: true, }; } -export function adminTableUrl(tableName: string): { - href: string; - external: boolean; -} { +export function adminTableUrl(tableName: string): ExternalLinkTarget { return { href: `/table/${encodeURIComponent(tableName)}`, external: false, diff --git a/apps/admin/src/pages/AdminMergePgc.tsx b/apps/admin/src/pages/AdminMergePgc.tsx index 87eb381..3160711 100644 --- a/apps/admin/src/pages/AdminMergePgc.tsx +++ b/apps/admin/src/pages/AdminMergePgc.tsx @@ -4,6 +4,7 @@ import { querySimple } from "@hyperleda/lib/clients/backend"; import { PgcObject, Schema } from "@hyperleda/lib/clients/backend"; import { adminClient } from "../clients"; import { backendClient } from "@hyperleda/lib/clients"; +import { describeUnknownError } from "@hyperleda/lib/tap"; import { publicObjectUrl } from "../origins"; import { AladinViewer, @@ -61,7 +62,7 @@ function objectLabel(object: PgcObject): string { function objectToSkySource(object: PgcObject, role: string): SkySource | null { const equatorial = object.catalogs.coordinates?.equatorial; - if (equatorial?.ra === undefined || equatorial?.dec === undefined) { + if (!equatorial) { return null; } @@ -87,20 +88,15 @@ async function fetchPgc( }, }); - if (response.error || !response.data) { - const err = response.error; + if (response.error) { throw new Error( - `Error during query: ${typeof err === "object" ? JSON.stringify(err) : err}`, + `Error during query: ${describeUnknownError(response.error)}`, ); } const objects = response.data.data.objects; const schema = response.data.data.schema; - const object = objects?.[0]; - - if (!object || Object.keys(object.catalogs).length === 0) { - throw new Error(`Object PGC ${pgc} not found`); - } + const object = objects[0]; return { object, schema }; } @@ -118,10 +114,9 @@ async function fetchByName( }, }); - if (response.error || !response.data) { - const err = response.error; + if (response.error) { throw new Error( - `Error during query: ${typeof err === "object" ? JSON.stringify(err) : err}`, + `Error during query: ${describeUnknownError(response.error)}`, ); } @@ -426,12 +421,8 @@ export function AdminMergePgcPage(): ReactElement { }, }); - if (response.error || !response.data?.data) { - throw new Error( - typeof response.error === "object" - ? JSON.stringify(response.error) - : String(response.error || "Unknown error"), - ); + if (response.error) { + throw new Error(describeUnknownError(response.error)); } const result = response.data.data; diff --git a/apps/admin/src/pages/CrossmatchResults.tsx b/apps/admin/src/pages/CrossmatchResults.tsx index 3e9ca23..a992aeb 100644 --- a/apps/admin/src/pages/CrossmatchResults.tsx +++ b/apps/admin/src/pages/CrossmatchResults.tsx @@ -101,6 +101,19 @@ interface CrossmatchResultsProps { loading?: boolean; } +function isRecordIndex(value: CellPrimitive): value is number { + return Number.isFinite(value) && Number.isInteger(value); +} + +function parseCrossmatchTriageStatus( + param: string, +): CrossmatchTriageStatus | null { + if (param === "pending" || param === "resolved" || param === "unprocessed") { + return param; + } + return null; +} + function CrossmatchResults({ data, loading, @@ -113,17 +126,24 @@ function CrossmatchResults({ function getCandidatePgcs(record: ApiRecord): number[] { const fromCandidates = record.crossmatch.candidates .map((candidate) => candidate.pgc) - .filter((pgc): pgc is number => typeof pgc === "number"); - - if (typeof record.pgc !== "number") { + .filter( + (pgc): pgc is number => Number.isFinite(pgc) && Number.isInteger(pgc), + ); + + const recordPgc = record.pgc; + if ( + recordPgc === null || + !Number.isFinite(recordPgc) || + !Number.isInteger(recordPgc) + ) { return fromCandidates; } - if (fromCandidates.includes(record.pgc)) { + if (fromCandidates.includes(recordPgc)) { return fromCandidates; } - return [record.pgc, ...fromCandidates]; + return [recordPgc, ...fromCandidates]; } function renderCandidates(record: ApiRecord): ReactElement { @@ -159,7 +179,7 @@ function CrossmatchResults({ { name: "Record name", renderCell: (recordIndex: CellPrimitive) => { - if (typeof recordIndex === "number" && data?.records[recordIndex]) { + if (isRecordIndex(recordIndex) && data?.records[recordIndex]) { return getRecordName(data.records[recordIndex]); } return NULL; @@ -169,7 +189,7 @@ function CrossmatchResults({ { name: "Candidates", renderCell: (recordIndex: CellPrimitive) => { - if (typeof recordIndex === "number" && data?.records[recordIndex]) { + if (isRecordIndex(recordIndex) && data?.records[recordIndex]) { return renderCandidates(data.records[recordIndex]); } return NULL; @@ -217,13 +237,43 @@ async function fetcher( ); } - if (!response.data) { - throw new Error("No data received from server"); - } - return response.data.data; } +interface CrossmatchContentProps { + data: GetRecordsResponse | null; + loading: boolean; + error: string | null; + page: number; + pageSize: number; + onPageChange: (page: number) => void; +} + +function CrossmatchContent({ + data, + loading, + error, + page, + pageSize, + onPageChange, +}: CrossmatchContentProps): ReactElement { + if (error && !data) return ; + if (!data?.records && loading) return ; + if (!data?.records) return ; + + return ( + <> + + + + ); +} + export function CrossmatchResultsPage(): ReactElement { const [searchParams, setSearchParams] = useSearchParams(); @@ -234,7 +284,7 @@ export function CrossmatchResultsPage(): ReactElement { ? "pending" : triageStatusParam === "all" ? null - : (triageStatusParam as CrossmatchTriageStatus); + : (parseCrossmatchTriageStatus(triageStatusParam) ?? "pending"); const page = parseInt(searchParams.get("page") || "0"); const pageSize = parseInt(searchParams.get("page_size") || "25"); @@ -278,24 +328,6 @@ export function CrossmatchResultsPage(): ReactElement { setSearchParams(newSearchParams); } - function Content(): ReactElement { - if (error && !data) return ; - if (!data?.records && loading) return ; - if (!data?.records) return ; - - return ( - <> - - - - ); - } - return ( <>

Crossmatch results

@@ -305,7 +337,14 @@ export function CrossmatchResultsPage(): ReactElement { pageSize={pageSize} onApplyFilters={handleApplyFilters} /> - + ); } diff --git a/apps/admin/src/pages/DataCatalog.tsx b/apps/admin/src/pages/DataCatalog.tsx index 08c9b4a..63621d2 100644 --- a/apps/admin/src/pages/DataCatalog.tsx +++ b/apps/admin/src/pages/DataCatalog.tsx @@ -49,9 +49,6 @@ async function fetchTablesList(): Promise { if (response.error) { throw new Error(formatApiError(response.error)); } - if (!response.data?.data) { - throw new Error("No table list received from server"); - } return response.data.data; } @@ -65,9 +62,6 @@ async function fetchTableRows(tableName: string): Promise { if (response.error) { throw new Error(formatApiError(response.error)); } - if (!response.data?.data) { - throw new Error("No table data received from server"); - } return response.data.data; } @@ -177,7 +171,7 @@ function columnMetadataHint(column: TapColumnInfo): ReactElement {
Type - {column.datatype ?? "—"} + {column.datatype} Unit @@ -356,13 +350,13 @@ export function DataCatalogPage(): ReactElement { function openSqlEditor(sql?: string): void { if (sql) { setSqlDraft(sql); - navigate({ + void navigate({ pathname: "/data-catalog/query", search: `?q=${encodeURIComponent(sql)}`, }); return; } - navigate({ pathname: "/data-catalog/query", search: "" }); + void navigate({ pathname: "/data-catalog/query", search: "" }); } function handleQueryRun(sql: string): void { @@ -375,7 +369,7 @@ export function DataCatalogPage(): ReactElement { setSqlDraft(defaultSelectForTable(nextTable)); return; } - navigate( + void navigate( `/data-catalog/${encodeURIComponent(nextSchema)}/${encodeURIComponent(nextTable)}`, ); } diff --git a/apps/admin/src/pages/Login.tsx b/apps/admin/src/pages/Login.tsx index e629cd4..f0e5dec 100644 --- a/apps/admin/src/pages/Login.tsx +++ b/apps/admin/src/pages/Login.tsx @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom"; import { setAuthToken } from "../auth/token"; import { login } from "../clients/admin/sdk.gen"; import { adminClient } from "../clients/index"; +import { formatCaughtError } from "@hyperleda/lib/tap"; import { Button } from "@hyperleda/lib/ui"; export function LoginPage(): ReactElement { @@ -30,17 +31,16 @@ export function LoginPage(): ReactElement { client: adminClient, body: { username, password }, }); - const token = response.data?.data?.token; - - if (response.error || !token) { + if (response.error) { throw new Error("Invalid username or password"); } + const token = response.data.data.token; setAuthToken(token); setSuccess(true); - navigate("/tables"); + void navigate("/tables"); } catch (submitError) { - setError(`${submitError}`); + setError(formatCaughtError(submitError)); } finally { setLoading(false); } diff --git a/apps/admin/src/pages/RecordCrossmatchDetails.tsx b/apps/admin/src/pages/RecordCrossmatchDetails.tsx index f2afb4f..db05eda 100644 --- a/apps/admin/src/pages/RecordCrossmatchDetails.tsx +++ b/apps/admin/src/pages/RecordCrossmatchDetails.tsx @@ -34,16 +34,17 @@ import { ObjectSummary, } from "../components/ui"; import { useDataFetching } from "@hyperleda/lib/hooks"; +import { describeUnknownError, formatCaughtError } from "@hyperleda/lib/tap"; function convertAdminSchemaToBackendSchema( adminSchema: AdminSchema, ): BackendSchema { function getCoordinateUnit(type: string, param: string): string { - return adminSchema.units.coordinates?.[type]?.[param] || "deg"; + return adminSchema.units.coordinates[type]?.[param] || "deg"; } function getVelocityUnit(type: string, param: string): string { - return adminSchema.units.velocity?.[type]?.[param] || "km/s"; + return adminSchema.units.velocity[type]?.[param] || "km/s"; } return { @@ -108,7 +109,7 @@ function createDescription( } function getCandidateLabel(candidate: PgcCandidate): string { - return candidate.catalogs?.designation?.name ?? `PGC ${candidate.pgc}`; + return candidate.catalogs.designation?.name ?? `PGC ${candidate.pgc}`; } function convertCandidatesToAdditionalSources( @@ -116,23 +117,23 @@ function convertCandidatesToAdditionalSources( mainRecord: RecordCrossmatch, ) { const candidateSources = candidates - .filter((candidate) => candidate.catalogs?.coordinates?.equatorial) + .filter((candidate) => candidate.catalogs.coordinates?.equatorial) .map((candidate) => ({ - ra: candidate.catalogs!.coordinates!.equatorial.ra, - dec: candidate.catalogs!.coordinates!.equatorial.dec, + ra: candidate.catalogs.coordinates!.equatorial.ra, + dec: candidate.catalogs.coordinates!.equatorial.dec, label: getCandidateLabel(candidate), description: createDescription( - candidate.catalogs?.velocity?.heliocentric, - candidate.catalogs?.redshift, + candidate.catalogs.velocity?.heliocentric, + candidate.catalogs.redshift, ), })); - const mainRecordSource = mainRecord.catalogs?.coordinates?.equatorial + const mainRecordSource = mainRecord.catalogs.coordinates?.equatorial ? { ra: mainRecord.catalogs.coordinates.equatorial.ra, dec: mainRecord.catalogs.coordinates.equatorial.dec, label: - mainRecord.catalogs?.designation?.name || + mainRecord.catalogs.designation?.name || `Record ${mainRecord.record_id}`, } : null; @@ -194,7 +195,7 @@ function ResolutionSelector({ try { await onAddCandidate(pgc); } catch (err) { - setAddCandidateError(`${err}`); + setAddCandidateError(formatCaughtError(err)); } } @@ -365,16 +366,15 @@ function ResolutionSelector({ ); } -function OriginalData({ - data, -}: { - data: { [key: string]: unknown }; -}): ReactElement { +type OriginalColumnValue = string | number | boolean | null; +type OriginalDataFields = Record; + +function OriginalData({ data }: { data: OriginalDataFields }): ReactElement { const columns: Column[] = [{ name: "Column" }, { name: "Value" }]; const tableData: Record[] = Object.entries(data).map( ([key, value]) => ({ Column: key, - Value: value === null || value === undefined ? "NULL" : String(value), + Value: value === null ? "NULL" : String(value), }), ); @@ -411,7 +411,7 @@ function RecordCrossmatchDetails({ `crossmatch.triage.verbose.${crossmatch.triage_status}`, ).Title; const objectName = - crossmatch.catalogs?.designation?.name ?? `Record ${crossmatch.record_id}`; + crossmatch.catalogs.designation?.name ?? `Record ${crossmatch.record_id}`; async function submitCrossmatchResolution( statuses: StatusesPayload, @@ -421,12 +421,8 @@ function RecordCrossmatchDetails({ body: { statuses }, }); - if (response.error || !response.data?.data) { - throw new Error( - typeof response.error === "object" - ? JSON.stringify(response.error) - : String(response.error || "Unknown error"), - ); + if (response.error) { + throw new Error(describeUnknownError(response.error)); } window.location.reload(); @@ -444,7 +440,7 @@ function RecordCrossmatchDetails({ }, }); } catch (err) { - setResolveError(`${err}`); + setResolveError(formatCaughtError(err)); } finally { setResolving(null); } @@ -461,7 +457,7 @@ function RecordCrossmatchDetails({ }, }); } catch (err) { - setResolveError(`${err}`); + setResolveError(formatCaughtError(err)); } finally { setResolving(null); } @@ -480,7 +476,7 @@ function RecordCrossmatchDetails({ }, }); } catch (err) { - setResolveError(`${err}`); + setResolveError(formatCaughtError(err)); throw err; } finally { setAddingCandidate(false); @@ -496,10 +492,13 @@ function RecordCrossmatchDetails({ } } + // SAFETY: Crossmatch original data is JSON scalar values keyed by column name. + const originalDataFields = originalData as OriginalDataFields | undefined; + return (
- {crossmatch.catalogs?.coordinates?.equatorial && ( + {crossmatch.catalogs.coordinates?.equatorial && ( )} - {originalData && ( + {originalDataFields && ( - + )}
@@ -590,9 +589,9 @@ async function fetcher( }, }); - if (response.error || !response.data?.data) { + if (response.error) { throw new Error( - `Error fetching crossmatch details: ${typeof response.error === "object" ? JSON.stringify(response.error) : response.error || "Unknown error"}`, + `Error fetching crossmatch details: ${describeUnknownError(response.error) || "Unknown error"}`, ); } @@ -611,13 +610,9 @@ export function RecordCrossmatchDetailsPage(): ReactElement { document.title = `Crossmatch - ${data?.crossmatch.catalogs.designation?.name ?? recordId} | HyperLEDA`; }, [data, recordId]); - function Content(): ReactElement { - if (loading) return ; - if (error) return ; - if (data) return ; - - return ; - } + if (loading) return ; + if (error) return ; + if (data) return ; - return ; + return ; } diff --git a/apps/admin/src/pages/SqlQuery.tsx b/apps/admin/src/pages/SqlQuery.tsx index 26d1903..67f0404 100644 --- a/apps/admin/src/pages/SqlQuery.tsx +++ b/apps/admin/src/pages/SqlQuery.tsx @@ -18,9 +18,6 @@ async function executeAdminSqlQuery(sql: string): Promise { if (response.error) { throw new Error(formatApiError(response.error)); } - if (!response.data?.data) { - throw new Error("No data received from server"); - } return response.data.data; } diff --git a/apps/admin/src/pages/TableDetails.tsx b/apps/admin/src/pages/TableDetails.tsx index afc0b5b..6b5b347 100644 --- a/apps/admin/src/pages/TableDetails.tsx +++ b/apps/admin/src/pages/TableDetails.tsx @@ -32,6 +32,7 @@ import { } from "../components/ui"; import { useDataFetching } from "@hyperleda/lib/hooks"; import { originalDataCatalogLink } from "@hyperleda/lib/astronomy"; +import { formatCaughtError } from "@hyperleda/lib/tap"; const DATA_TYPES: DataType[] = [ "regular", @@ -42,7 +43,7 @@ const DATA_TYPES: DataType[] = [ const TABLE_STATUSES: TableStatus[] = ["initiated", "archived"]; -function asDataType(value: unknown): DataType { +function asDataType(value: string): DataType { if ( value === "regular" || value === "reprocessing" || @@ -54,7 +55,7 @@ function asDataType(value: unknown): DataType { return "regular"; } -function asTableStatus(value: unknown): TableStatus { +function asTableStatus(value: string): TableStatus { if (value === "initiated" || value === "archived") { return value; } @@ -114,8 +115,12 @@ function renderTime(time: string): string { }); } +function isCellString(value: CellPrimitive): value is string { + return Object(value) !== value && !Number.isFinite(value); +} + function renderUCD(ucd: CellPrimitive): ReactElement { - if (!(typeof ucd === "string")) { + if (!isCellString(ucd)) { return
; } @@ -197,7 +202,7 @@ function TableMeta(props: TableMetaProps): ReactElement { } onSuccess(); } catch (err) { - setPatchError(`${err}`); + setPatchError(formatCaughtError(err)); throw err; } finally { setSavingField(null); @@ -214,7 +219,9 @@ function TableMeta(props: TableMetaProps): ReactElement { table_name: props.tableName, new_table_name: trimmed, }, - () => navigate(`/table/${encodeURIComponent(trimmed)}`), + () => { + void navigate(`/table/${encodeURIComponent(trimmed)}`); + }, ); } @@ -230,7 +237,7 @@ function TableMeta(props: TableMetaProps): ReactElement { } async function commitDatatype(next: DataType): Promise { - const current = asDataType(props.table.meta.datatype); + const current = asDataType(String(props.table.meta.datatype)); if (next === current) { return; } @@ -245,7 +252,7 @@ function TableMeta(props: TableMetaProps): ReactElement { } async function commitStatus(next: TableStatus): Promise { - const current = asTableStatus(props.table.meta.status); + const current = asTableStatus(String(props.table.meta.status)); if (next === current) { return; } @@ -261,8 +268,8 @@ function TableMeta(props: TableMetaProps): ReactElement { const datatypeControl = canEdit ? ( void commitStatus(event.target.value as TableStatus)} + onChange={(event) => void commitStatus(asTableStatus(event.target.value))} disabled={savingField !== null} className="bg-surface-2 border border-border rounded px-2 py-1 text-primary max-w-xs" > @@ -330,7 +337,7 @@ function TableMeta(props: TableMetaProps): ReactElement { {datatypeControl} {statusControl} - {renderTime(props.table.meta.modification_dt as string)} + {renderTime(String(props.table.meta.modification_dt))} {patchError ? ( <> @@ -481,22 +488,17 @@ function CatalogProgressCard({ ); })} - {selectedProgress ? ( -
- - {formatProgressValue(selectedProgress.structured, totalRecords)} - - - {formatProgressValue(selectedProgress.in_layer2, totalRecords)} - - - {formatProgressValue( - selectedProgress.layer2_pending, - totalRecords, - )} - -
- ) : null} +
+ + {formatProgressValue(selectedProgress.structured, totalRecords)} + + + {formatProgressValue(selectedProgress.in_layer2, totalRecords)} + + + {formatProgressValue(selectedProgress.layer2_pending, totalRecords)} + +
); @@ -522,7 +524,9 @@ function columnMatchesSearch( const fields = [col.name, col.description, col.ucd]; return fields.some( (value) => - typeof value === "string" && value.toLowerCase().includes(needle), + value !== null && + value !== undefined && + value.toLowerCase().includes(needle), ); } @@ -586,7 +590,7 @@ function ColumnInfo(props: ColumnInfoProps): ReactElement { } props.onAfterPatch(); } catch (err) { - setPatchError(`${err}`); + setPatchError(formatCaughtError(err)); throw err; } finally { setSaving(null); @@ -634,7 +638,7 @@ function ColumnInfo(props: ColumnInfoProps): ReactElement { { title: "View table data", onClick: () => { - navigate( + void navigate( originalDataCatalogLink( selectAllColumnsFromRawdataTable( props.tableName, @@ -760,47 +764,45 @@ export function TableDetailsPage(): ReactElement { error, } = useDataFetching(() => fetcher(tableName), [tableName, refreshKey]); - function Content(): ReactElement { - if (loading) return ; - if (error) return ; - if (payload) { - return ( -
-
- setRefreshKey((key) => key + 1)} - className="lg:col-span-6" - /> - 0 - ? "lg:col-span-3" - : "lg:col-span-6" - } - /> - {Object.keys(payload.progress.catalogs).length > 0 ? ( - - ) : null} -
- setRefreshKey((key) => key + 1)} - /> -
- ); - } - - return ; + function onAfterPatch(): void { + setRefreshKey((key) => key + 1); } - return ; + if (loading) return ; + if (error) return ; + if (!payload) return ; + + return ( +
+
+ + 0 + ? "lg:col-span-3" + : "lg:col-span-6" + } + /> + {Object.keys(payload.progress.catalogs).length > 0 ? ( + + ) : null} +
+ +
+ ); } diff --git a/apps/admin/src/pages/Tables.tsx b/apps/admin/src/pages/Tables.tsx index 2af3765..03d9d58 100644 --- a/apps/admin/src/pages/Tables.tsx +++ b/apps/admin/src/pages/Tables.tsx @@ -7,7 +7,6 @@ import type { TableListItem, TableProgress, TableStatus, - ValidationError, } from "../clients/admin"; import { adminClient } from "../clients"; import { @@ -26,6 +25,7 @@ import { TextFilter, } from "../components/ui"; import { useDataFetching } from "@hyperleda/lib/hooks"; +import { formatApiError } from "@hyperleda/lib/tap"; import { getSourceLink } from "@hyperleda/lib/astronomy"; const SEARCH_DEBOUNCE_MS = 300; @@ -35,18 +35,19 @@ const TABLE_STATUS_OPTIONS: { value: TableStatus; label: string }[] = [ { value: "archived", label: "Archived" }, ]; +function isTableStatus(value: string): value is TableStatus { + return value === "initiated" || value === "archived"; +} + function parseStatusesParam(param: string | null): TableStatus[] { if (!param) { return []; } - const validStatuses = new Set(["initiated", "archived"]); return param .split(",") .map((value) => value.trim()) - .filter((value): value is TableStatus => - validStatuses.has(value as TableStatus), - ); + .filter(isTableStatus); } function formatTableStatusLabel(status: TableStatus): string { @@ -177,7 +178,9 @@ function TableListCard({ table }: { table: TableListItem }): ReactElement { const actions: CardAction[] = [ { title: "View crossmatch results", - onClick: () => navigate(crossmatchListHref(table.name)), + onClick: () => { + void navigate(crossmatchListHref(table.name)); + }, }, ]; @@ -276,20 +279,46 @@ async function fetcher( }); if (response.error) { - throw new Error( - (response.error as { detail?: ValidationError[] }).detail - ?.map((err: ValidationError) => err.msg) - .join(", ") || "Failed to fetch tables", - ); - } - - if (!response.data) { - throw new Error("No data received from server"); + throw new Error(formatApiError(response.error) || "Failed to fetch tables"); } return response.data.data; } +interface TablesContentProps { + data: GetTableListResponse | null; + loading: boolean; + error: string | null; + page: number; + pageSize: number; + onPageChange: (page: number) => void; +} + +function TablesContent({ + data, + loading, + error, + page, + pageSize, + onPageChange, +}: TablesContentProps): ReactElement { + if (error && !data) return ; + if (!data?.tables && loading) return ; + if (!data?.tables) return ; + + return ( + <> + + + + ); +} + export function TablesPage(): ReactElement { const [searchParams, setSearchParams] = useSearchParams(); @@ -340,24 +369,6 @@ export function TablesPage(): ReactElement { setSearchParams(newSearchParams); } - function Content(): ReactElement { - if (error && !data) return ; - if (!data?.tables && loading) return ; - if (!data?.tables) return ; - - return ( - <> - - - - ); - } - return ( <>

Tables

@@ -371,7 +382,14 @@ export function TablesPage(): ReactElement { updateParams({ statuses: nextStatuses }) } /> - + ); } diff --git a/apps/admin/src/resources/resources.tsx b/apps/admin/src/resources/resources.tsx index 8ae2e3c..1bcaac7 100644 --- a/apps/admin/src/resources/resources.tsx +++ b/apps/admin/src/resources/resources.tsx @@ -5,6 +5,7 @@ export interface Resource { } export function getResource(key: string): Resource { + // SAFETY: `key` is checked with `in` before indexing `texts.title`. const titleProp = key as keyof typeof texts.title; if (titleProp in texts.title) { diff --git a/apps/web/src/components/catalogs/AstrometryCard.tsx b/apps/web/src/components/catalogs/AstrometryCard.tsx index f208091..8d11b4f 100644 --- a/apps/web/src/components/catalogs/AstrometryCard.tsx +++ b/apps/web/src/components/catalogs/AstrometryCard.tsx @@ -48,29 +48,23 @@ export function AstrometryCard({ anchorId?: string; className?: string; }): ReactElement { - const equatorial = catalogs?.coordinates?.equatorial; - const galactic = catalogs?.coordinates?.galactic; - const supergalactic = catalogs?.coordinates?.supergalactic; - const hasEquatorial = - equatorial?.ra !== undefined || equatorial?.dec !== undefined; - const hasGalactic = - galactic?.lon !== undefined || galactic?.lat !== undefined; - const hasSupergalactic = - supergalactic?.lon !== undefined || supergalactic?.lat !== undefined; - const hasPrecision = - equatorial?.e_ra !== undefined || equatorial?.e_dec !== undefined; - const hasData = hasEquatorial || hasGalactic || hasSupergalactic; + const coordinates = catalogs.coordinates; + const hasData = coordinates !== null && coordinates !== undefined; const actions: CatalogCardAction[] = []; - if (equatorial?.ra !== undefined && equatorial?.dec !== undefined) { + if (coordinates !== null && coordinates !== undefined) { for (const { id, title } of EQUATORIAL_COPY_FORMATS) { actions.push({ title: `Copy ICRS as ${title}`, icon: MdContentCopy, onClick: () => { void navigator.clipboard.writeText( - formatEquatorialForCopy(equatorial.ra, equatorial.dec, id), + formatEquatorialForCopy( + coordinates.equatorial.ra, + coordinates.equatorial.dec, + id, + ), ); }, }); @@ -79,27 +73,15 @@ export function AstrometryCard({ actions.push({ title: "Search in NED", icon: MdSearch, - href: buildNedPositionSearchUrl(equatorial.ra, equatorial.dec), + href: buildNedPositionSearchUrl( + coordinates.equatorial.ra, + coordinates.equatorial.dec, + ), }); } - const raUnit = schema.units.coordinates?.equatorial?.ra || "deg"; - const eRaUnit = schema.units.coordinates?.equatorial?.e_ra || raUnit; - const eDecUnit = schema.units.coordinates?.equatorial?.e_dec || raUnit; - const precisionErrors = [equatorial?.e_ra, equatorial?.e_dec].filter( - (value): value is number => value !== undefined, - ); - const precision = - precisionErrors.length > 0 - ? precisionErrors.reduce((sum, value) => sum + value, 0) / - precisionErrors.length - : undefined; - const precisionUnit = - equatorial?.e_ra !== undefined - ? eRaUnit - : equatorial?.e_dec !== undefined - ? eDecUnit - : eRaUnit; + const raUnit = schema.units.coordinates.equatorial.ra || "deg"; + const eRaUnit = schema.units.coordinates.equatorial.e_ra || raUnit; return ( {!hasData && } - {hasEquatorial && ( + {coordinates !== null && coordinates !== undefined && ( <> - {equatorial?.ra !== undefined && ( - - )} - {equatorial?.dec !== undefined && ( - - )} + + - {equatorial?.ra !== undefined && equatorial?.dec !== undefined && ( - - - - )} - - )} - {hasGalactic && ( - - - {galactic?.lon !== undefined && ( + + + + + - )} - {galactic?.lat !== undefined && ( - )} - - - )} - {hasSupergalactic && ( - - - {supergalactic?.lon !== undefined && ( + + + + - )} - {supergalactic?.lat !== undefined && ( - )} - - - )} - {hasPrecision && precision !== undefined && ( - - ± - + + + + ±{" "} + + + )} ); diff --git a/apps/web/src/components/catalogs/IdentityCard.tsx b/apps/web/src/components/catalogs/IdentityCard.tsx index d7a5133..78242f2 100644 --- a/apps/web/src/components/catalogs/IdentityCard.tsx +++ b/apps/web/src/components/catalogs/IdentityCard.tsx @@ -22,9 +22,10 @@ export function IdentityCard({ anchorId?: string; className?: string; }): ReactElement { - const name = catalogs?.designation?.name || `PGC ${pgc}`; - const redshift = catalogs?.redshift; - const heliocentric = catalogs?.velocity?.heliocentric; + const name = catalogs.designation?.name || `PGC ${pgc}`; + const redshift = catalogs.redshift; + const heliocentric = catalogs.velocity?.heliocentric; + const heliocentricUnits = schema.units.velocity.heliocentric; const ohpMirrorUrl = `http://atlas.obs-hp.fr/hyperleda/ledacat.cgi?o=%23${pgc}`; const identityActions: CatalogCardAction[] = [ { @@ -42,10 +43,10 @@ export function IdentityCard({ className={className} > {pgc} - {catalogs?.nature?.type_name && ( + {catalogs.nature?.type_name && ( {catalogs.nature.type_name} )} - {catalogs?.additional_designations && + {catalogs.additional_designations && catalogs.additional_designations.length > 0 && (
    @@ -68,22 +69,22 @@ export function IdentityCard({
)} - {redshift?.z !== undefined && ( + {redshift !== null && redshift !== undefined && ( {redshift.z.toFixed(5)} )} - {heliocentric?.v !== undefined && ( + {heliocentric !== undefined && ( diff --git a/apps/web/src/components/catalogs/KinematicsCard.tsx b/apps/web/src/components/catalogs/KinematicsCard.tsx index 7b7fd1d..c07d866 100644 --- a/apps/web/src/components/catalogs/KinematicsCard.tsx +++ b/apps/web/src/components/catalogs/KinematicsCard.tsx @@ -21,6 +21,37 @@ FROM cz.data AS c WHERE r.pgc = ${pgc}`; } +type VelocityKey = "heliocentric" | "local_group" | "cmb_old" | "cmb"; + +const VELOCITY_FIELDS: { key: VelocityKey; label: string }[] = [ + { key: "heliocentric", label: "Heliocentric" }, + { key: "local_group", label: "Local Group" }, + { key: "cmb_old", label: "CMB (old)" }, + { key: "cmb", label: "CMB" }, +]; + +function velocityField( + key: VelocityKey, + label: string, + velocity: NonNullable, + schema: Schema, +): ReactElement | null { + if (!(key in velocity)) { + return null; + } + + const measurement = velocity[key]; + + const units = schema.units.velocity[key]; + return ( + + + + + + ); +} + export function KinematicsCard({ catalogs, schema, @@ -34,67 +65,16 @@ export function KinematicsCard({ anchorId?: string; className?: string; }): ReactElement { - const redshift = catalogs?.redshift; - const velocity = catalogs?.velocity; + const redshift = catalogs.redshift; + const velocity = catalogs.velocity; const velocityFields = velocity - ? [ - velocity.heliocentric?.v !== undefined && ( - - - - - - ), - velocity.local_group?.v !== undefined && ( - - - - - - ), - velocity.cmb_old?.v !== undefined && ( - - - - - - ), - velocity.cmb?.v !== undefined && ( - - - - - - ), - ].filter(Boolean) + ? VELOCITY_FIELDS.map(({ key, label }) => + velocityField(key, label, velocity, schema), + ).filter((field): field is ReactElement => field !== null) : []; - const hasRedshift = redshift?.z !== undefined; + const hasRedshift = redshift !== null && redshift !== undefined; const hasData = hasRedshift || velocityFields.length > 0; return ( diff --git a/apps/web/src/components/catalogs/PhotometryTotalCard.tsx b/apps/web/src/components/catalogs/PhotometryTotalCard.tsx index a0df6c9..f196236 100644 --- a/apps/web/src/components/catalogs/PhotometryTotalCard.tsx +++ b/apps/web/src/components/catalogs/PhotometryTotalCard.tsx @@ -39,7 +39,7 @@ function formatPhotometryDetails( const lines = [ `Band: ${measurement.band}`, `λ: ${measurement.wavelength} Å`, - `mag: ${measurement.mag}${measurement.e_mag !== null && measurement.e_mag !== undefined ? ` ± ${measurement.e_mag}` : ""}`, + `mag: ${measurement.mag}${measurement.e_mag !== null ? ` ± ${measurement.e_mag}` : ""}`, `Method: ${measurement.method}`, ]; @@ -86,7 +86,7 @@ export function PhotometryTotalCard({ originalDataSql={hasData ? photometryTotalSqlQuery(pgc) : undefined} className={className} > - {hasData && plotProps ? : } + {hasData ? : } ); } diff --git a/apps/web/src/components/catalogs/SkyViewCard.tsx b/apps/web/src/components/catalogs/SkyViewCard.tsx index 7c7e13d..4f728e4 100644 --- a/apps/web/src/components/catalogs/SkyViewCard.tsx +++ b/apps/web/src/components/catalogs/SkyViewCard.tsx @@ -12,9 +12,8 @@ export function SkyViewCard({ anchorId?: string; className?: string; }): ReactElement { - const equatorial = catalogs?.coordinates?.equatorial; - const hasCoordinates = - equatorial?.ra !== undefined && equatorial?.dec !== undefined; + const coordinates = catalogs.coordinates; + const hasCoordinates = coordinates !== null && coordinates !== undefined; return ( {hasCoordinates ? ( diff --git a/apps/web/src/components/core/Plot.tsx b/apps/web/src/components/core/Plot.tsx index 2a0d71b..f190a9a 100644 --- a/apps/web/src/components/core/Plot.tsx +++ b/apps/web/src/components/core/Plot.tsx @@ -81,15 +81,11 @@ function yRangeWithErrors( if (yErrors) { for (let i = 0; i < y.length; i++) { const err = yErrors[i]; - if (err === null || err === undefined || err <= 0) { + if (err === null || err <= 0) { continue; } const yVal = y[i]; - if (yVal === null || yVal === undefined) { - continue; - } - min = Math.min(min, yVal - err); max = Math.max(max, yVal + err); } @@ -99,18 +95,34 @@ function yRangeWithErrors( }; } +interface PlotColors { + text: string; + subtle: string; + grid: string; + accent: string; +} + +interface AxisStrokeOptions { + stroke: string; + font: string; + labelFont: string; + grid: uPlot.Axis.Grid; + ticks: uPlot.Axis.Ticks; + border: uPlot.Axis.Border; +} + +interface PointTooltipPosition { + left: number; + top: number; +} + function readCssToken(name: string): string { return getComputedStyle(document.documentElement) .getPropertyValue(name) .trim(); } -function getPlotColors(): { - text: string; - subtle: string; - grid: string; - accent: string; -} { +function getPlotColors(): PlotColors { return { text: readCssToken("--token-primary"), subtle: readCssToken("--token-subtle"), @@ -119,14 +131,7 @@ function getPlotColors(): { }; } -function axisStrokeOptions(colors: ReturnType): { - stroke: string; - font: string; - labelFont: string; - grid: uPlot.Axis.Grid; - ticks: uPlot.Axis.Ticks; - border: uPlot.Axis.Border; -} { +function axisStrokeOptions(colors: PlotColors): AxisStrokeOptions { return { stroke: colors.text, font: AXIS_VALUE_FONT, @@ -160,15 +165,8 @@ function findNearestPointIndex( for (let i = 0; i < xData.length; i++) { const xVal = xData[i]; - const yVal = yData[i]; - if ( - xVal === null || - xVal === undefined || - yVal === null || - yVal === undefined - ) { - continue; - } + // SAFETY: Plot series are built from numeric arrays without null entries. + const yVal = yData[i] as number; const pointLeft = u.valToPos(xVal, "x"); const pointTop = u.valToPos(yVal, "y"); @@ -187,8 +185,10 @@ function getPointTooltipPosition( u: uPlot, wrapper: HTMLElement, index: number, -): { left: number; top: number } { - const xVal = u.data[0][index] as number; +): PointTooltipPosition { + // SAFETY: `index` comes from uPlot data aligned with numeric series values. + const xVal = u.data[0][index]; + // SAFETY: `index` comes from uPlot data aligned with numeric series values. const yVal = u.data[1][index] as number; const overRect = u.over.getBoundingClientRect(); const wrapperRect = wrapper.getBoundingClientRect(); @@ -218,20 +218,13 @@ function drawYErrorBars( for (let i = 0; i < xData.length; i++) { const err = yErrors[i]; - if (err === null || err === undefined || err <= 0) { + if (err === null || err <= 0) { continue; } const xVal = xData[i]; - const yVal = yData[i]; - if ( - xVal === null || - xVal === undefined || - yVal === null || - yVal === undefined - ) { - continue; - } + // SAFETY: Plot series are built from numeric arrays without null entries. + const yVal = yData[i] as number; const xPos = u.valToPos(xVal, "x", true); const yTop = u.valToPos(yVal + err, "y", true); @@ -337,6 +330,12 @@ export function PlotView({ const axisStyle = axisStrokeOptions(colors); const topPadding = vlines.length > 0 ? VLINE_TOP_PADDING : 0; + const yScale: uPlot.Scale = { + range: yRangeWithErrors(aligned.y, aligned.yErrors), + }; + if (invertY) { + yScale.dir = -1; + } const options: uPlot.Options = { width: container.clientWidth || container.offsetWidth, @@ -353,10 +352,7 @@ export function PlotView({ } : { range: paddedXRange }), }, - y: { - range: yRangeWithErrors(aligned.y, aligned.yErrors), - ...(invertY ? { dir: -1 } : {}), - }, + y: yScale, }, axes: [ { @@ -433,13 +429,9 @@ export function PlotView({ plotRef.current.over.addEventListener("mouseleave", handleMouseLeave); const resizeObserver = new ResizeObserver(() => { - if (!plotRef.current || !container) { - return; - } - const width = container.clientWidth || container.offsetWidth; if (width > 0) { - plotRef.current.setSize({ width, height: PLOT_HEIGHT }); + plotRef.current?.setSize({ width, height: PLOT_HEIGHT }); } }); resizeObserver.observe(container); @@ -539,11 +531,8 @@ export class PlotBuilder { return this; } - toProps(className?: string): PlotViewProps | null { + toProps(className?: string): PlotViewProps { const primary = this.series[0]; - if (!primary) { - return null; - } return { series: primary, diff --git a/apps/web/src/components/ui/Navbar.tsx b/apps/web/src/components/ui/Navbar.tsx index 3eeec0b..383ef29 100644 --- a/apps/web/src/components/ui/Navbar.tsx +++ b/apps/web/src/components/ui/Navbar.tsx @@ -23,9 +23,6 @@ export function Navbar() { const infoButtonRef = useRef(null); const showOpenProductionButton = useMemo(() => { - if (!configuredProductionWeb) { - return false; - } try { return window.location.origin !== new URL(configuredProductionWeb).origin; } catch { @@ -36,7 +33,9 @@ export function Navbar() { useEffect(() => { function handleClickOutside(e: MouseEvent) { const clickedInside = + // SAFETY: `contains` expects a DOM Node; event targets inside the document are Nodes. infoPanelRef.current?.contains(e.target as Node) || + // SAFETY: `contains` expects a DOM Node; event targets inside the document are Nodes. infoButtonRef.current?.contains(e.target as Node); if (!clickedInside) { setFooterOpen(false); @@ -75,12 +74,7 @@ export function Navbar() { } > {navItems.map((item) => ( - + {item.icon} ))} diff --git a/apps/web/src/components/ui/Searchbar.tsx b/apps/web/src/components/ui/Searchbar.tsx index abcac05..f6b03cf 100644 --- a/apps/web/src/components/ui/Searchbar.tsx +++ b/apps/web/src/components/ui/Searchbar.tsx @@ -16,7 +16,7 @@ interface SearchBarProps { function searchHandler(navigate: NavigateFunction) { return function f(query: string) { - navigate(`/query?q=${encodeURIComponent(query)}`); + void navigate(`/query?q=${encodeURIComponent(query)}`); }; } diff --git a/apps/web/src/lib/astronomy/equatorial.ts b/apps/web/src/lib/astronomy/equatorial.ts index b401c10..cb64077 100644 --- a/apps/web/src/lib/astronomy/equatorial.ts +++ b/apps/web/src/lib/astronomy/equatorial.ts @@ -3,9 +3,7 @@ import { decomposeDec, decomposeRa, pad2 } from "@hyperleda/lib/astronomy"; function formatSexagesimalSeconds(seconds: number, decimals: number): string { const fixed = seconds.toFixed(decimals); const [integerPart, fractionalPart] = fixed.split("."); - return fractionalPart === undefined - ? pad2(Number(integerPart)) - : `${pad2(Number(integerPart))}.${fractionalPart}`; + return `${pad2(Number(integerPart))}.${fractionalPart}`; } export type EquatorialCopyFormat = diff --git a/apps/web/src/lib/astronomy/parseCoordinateQuery.ts b/apps/web/src/lib/astronomy/parseCoordinateQuery.ts index 1a88f81..b19f64c 100644 --- a/apps/web/src/lib/astronomy/parseCoordinateQuery.ts +++ b/apps/web/src/lib/astronomy/parseCoordinateQuery.ts @@ -43,41 +43,58 @@ type Prefix = "J" | "B" | "G" | "S"; function systemFromPrefix(prefix: Prefix | null): CoordinateSystem { switch (prefix) { + case "J": + return "j2000"; case "B": return "b1950"; case "G": return "galactic"; case "S": return "supergalactic"; - default: + case null: return "j2000"; + default: { + const unreachable: never = prefix; + return unreachable; + } } } function systemLabel(system: CoordinateSystem): string { switch (system) { + case "j2000": + return "J2000"; case "b1950": return "B1950"; case "galactic": return "Galactic"; case "supergalactic": return "Supergalactic"; - default: - return "J2000"; + default: { + const unreachable: never = system; + return unreachable; + } } } -function axisLabels(system: CoordinateSystem): { +interface CoordinateAxisLabels { first: string; second: string; -} { +} + +function axisLabels(system: CoordinateSystem): CoordinateAxisLabels { switch (system) { + case "j2000": + case "b1950": + return { first: "RA", second: "Dec" }; case "galactic": return { first: "l", second: "b" }; case "supergalactic": return { first: "SGL", second: "SGB" }; - default: - return { first: "RA", second: "Dec" }; + default: { + const unreachable: never = system; + return unreachable; + } } } @@ -94,7 +111,7 @@ function isSexagesimalToken(token: string): boolean { return integerDigitCount(token) >= 4; } -function looksCoordinateShaped(body: string): boolean { +function matchesCoordinateTokenPattern(body: string): boolean { return /^(\d+\.?\d*)?([+-]\d*\.?\d*)?$/.test(body); } @@ -292,6 +309,13 @@ function buildQuery( lat, toQueryParams(): CoordinateQueryParams { switch (system) { + case "j2000": + return { + ra: lon, + dec: lat, + eq_epoch: "J2000", + radius: ARCMINUTE_RADIUS_DEG, + }; case "b1950": return { ra: lon, @@ -303,13 +327,10 @@ function buildQuery( return { glon: lon, glat: lat, radius: ARCMINUTE_RADIUS_DEG }; case "supergalactic": return { sgl: lon, sgb: lat, radius: ARCMINUTE_RADIUS_DEG }; - default: - return { - ra: lon, - dec: lat, - eq_epoch: "J2000", - radius: ARCMINUTE_RADIUS_DEG, - }; + default: { + const unreachable: never = system; + return unreachable; + } } }, }; @@ -408,6 +429,7 @@ function inspectSexagesimalHms( const lat = sign && deg !== undefined ? degreesMinutesSecondsToDegrees( + // SAFETY: The sexagesimal regex captures only "+" or "-" in this group. sign as "+" | "-", Number(deg), Number(arcmin ?? "0"), @@ -626,7 +648,7 @@ function tryParseEquatorialCopyFormats( return null; } -type ParsedShape = { +type CoordinateInputParts = { prefix: Prefix | null; firstToken: string; sign: "+" | "-" | null; @@ -634,7 +656,7 @@ type ParsedShape = { hasSeparator: boolean; }; -function splitInput(trimmed: string): ParsedShape | null { +function splitInput(trimmed: string): CoordinateInputParts | null { if (!trimmed) { return null; } @@ -644,13 +666,14 @@ function splitInput(trimmed: string): ParsedShape | null { let body = trimmed; if (prefixMatch) { + // SAFETY: The prefix regex only matches J, B, G, or S. prefix = prefixMatch[1].toUpperCase() as Prefix; body = prefixMatch[2]; } else if (!/^\d/.test(trimmed)) { return null; } - if (body.length > 0 && !looksCoordinateShaped(body)) { + if (body.length > 0 && !matchesCoordinateTokenPattern(body)) { return null; } @@ -681,6 +704,7 @@ function splitInput(trimmed: string): ParsedShape | null { return { prefix, firstToken: body.slice(0, sepIndex), + // SAFETY: `sepIndex` comes from the first "+" or "-" separator in the body. sign: body[sepIndex] as "+" | "-", secondToken: body.slice(sepIndex + 1), hasSeparator: true, @@ -694,25 +718,30 @@ export function inspectCoordinateQuery(input: string): CoordinateInspect { return fromCopyFormats; } - const shape = splitInput(trimmed); - if (!shape) { + const inputParts = splitInput(trimmed); + if (!inputParts) { return { status: "none" }; } - const system = systemFromPrefix(shape.prefix); + const system = systemFromPrefix(inputParts.prefix); const labels = axisLabels(system); const firstLon = - shape.firstToken.length > 0 - ? parseFirstAxis(system, shape.firstToken) + inputParts.firstToken.length > 0 + ? parseFirstAxis(system, inputParts.firstToken) : null; const firstWasSexagesimal = - shape.firstToken.length > 0 && isSexagesimalToken(shape.firstToken); + inputParts.firstToken.length > 0 && + isSexagesimalToken(inputParts.firstToken); let secondLat: number | null = null; - if (shape.hasSeparator && shape.sign && shape.secondToken.length > 0) { + if ( + inputParts.hasSeparator && + inputParts.sign && + inputParts.secondToken.length > 0 + ) { secondLat = parseSecondAxis( - shape.secondToken, - shape.sign, + inputParts.secondToken, + inputParts.sign, firstWasSexagesimal, ); } diff --git a/apps/web/src/lib/astronomy/photometryFilters.ts b/apps/web/src/lib/astronomy/photometryFilters.ts index f36b1a5..0788fb6 100644 --- a/apps/web/src/lib/astronomy/photometryFilters.ts +++ b/apps/web/src/lib/astronomy/photometryFilters.ts @@ -22,6 +22,7 @@ interface PhotometricBandGroup { bands: PhotometricBandConstant[]; } +// SAFETY: `constants.json` is checked in at build time with this schema. const photometricBands = constants.photometricBands as Record< PhotometricMagsysGroup, PhotometricBandGroup diff --git a/apps/web/src/lib/search/searchTypes.ts b/apps/web/src/lib/search/searchTypes.ts index 669708b..7f2fc20 100644 --- a/apps/web/src/lib/search/searchTypes.ts +++ b/apps/web/src/lib/search/searchTypes.ts @@ -14,6 +14,11 @@ export type SearchTypePartial = { hint: string; }; +interface SearchTypeInspection { + eligible: { type: SearchType; suggestion: string }[]; + partial: { type: SearchType; hint: string }[]; +} + export type SearchType = { id: string; title: string; @@ -108,10 +113,7 @@ export function resolveEligibleSearchTypes(query: string): SearchType[] { return SEARCH_TYPES.filter((type) => type.suggest(trimmed) !== null); } -export function inspectSearchTypes(query: string): { - eligible: { type: SearchType; suggestion: string }[]; - partial: { type: SearchType; hint: string }[]; -} { +export function inspectSearchTypes(query: string): SearchTypeInspection { const trimmed = query.trim(); if (!trimmed) { return { eligible: [], partial: [] }; diff --git a/apps/web/src/pages/ObjectDetails.tsx b/apps/web/src/pages/ObjectDetails.tsx index 4b2bb7c..592ed47 100644 --- a/apps/web/src/pages/ObjectDetails.tsx +++ b/apps/web/src/pages/ObjectDetails.tsx @@ -13,15 +13,14 @@ import { querySimple } from "@hyperleda/lib/clients/backend"; import { PgcObject, Schema } from "@hyperleda/lib/clients/backend"; import { useDataFetching } from "@hyperleda/lib/hooks"; import { backendClient } from "@hyperleda/lib/clients"; +import { describeUnknownError } from "@hyperleda/lib/tap"; interface ObjectDetailsProps { object: PgcObject; - schema: Schema | null; + schema: Schema; } function ObjectDetails({ object, schema }: ObjectDetailsProps): ReactElement { - if (!object || !schema) return
; - const catalogs = object.catalogs; return ( @@ -81,17 +80,16 @@ async function fetcher( }, }); - if (response.error || !response.data) { - const err = response.error; + if (response.error) { throw new Error( - `Error during query: ${typeof err === "object" ? JSON.stringify(err) : err}`, + `Error during query: ${describeUnknownError(response.error)}`, ); } const objects = response.data.data.objects; const schema = response.data.data.schema; - if (!objects || objects.length === 0) { + if (objects.length === 0) { throw new Error(`Object ${pgcId} not found`); } @@ -111,16 +109,12 @@ export function ObjectDetailsPage(): ReactElement { error, } = useDataFetching(() => fetcher(pgcId), [pgcId]); - const [object, schema] = payload || [null, null]; - - function Content(): ReactElement { - if (loading) return ; - if (error) return ; - if (object && schema) - return ; - - return ; + if (loading) return ; + if (error) return ; + if (payload) { + const [object, schema] = payload; + return ; } - return ; + return ; } diff --git a/apps/web/src/pages/SearchResults.tsx b/apps/web/src/pages/SearchResults.tsx index 25f1f9f..14c2c28 100644 --- a/apps/web/src/pages/SearchResults.tsx +++ b/apps/web/src/pages/SearchResults.tsx @@ -26,6 +26,7 @@ import { } from "@hyperleda/lib/ui"; import { Button } from "@hyperleda/lib/ui"; import { backendClient } from "@hyperleda/lib/clients"; +import { describeUnknownError } from "@hyperleda/lib/tap"; import { resolveEligibleSearchTypes, SearchType, @@ -36,7 +37,7 @@ const ALADIN_FOV_PADDING = 1.4; function searchHandler(navigate: NavigateFunction) { return function f(query: string) { - navigate(`/query?q=${encodeURIComponent(query)}`); + void navigate(`/query?q=${encodeURIComponent(query)}`); }; } @@ -46,7 +47,7 @@ function pageChangeHandler( pageSize: number, newPage: number, ) { - navigate( + void navigate( `/query?q=${encodeURIComponent(query)}&page=${newPage}&pagesize=${pageSize}`, ); } @@ -76,15 +77,15 @@ type MultiSearchResults = { function objectsToSkySources(objects: PgcObject[]): SkySource[] { return objects.flatMap((object) => { - const equatorial = object.catalogs.coordinates?.equatorial; - if (equatorial?.ra === undefined || equatorial?.dec === undefined) { + const coordinates = object.catalogs.coordinates; + if (coordinates === null || coordinates === undefined) { return []; } return [ { - ra: equatorial.ra, - dec: equatorial.dec, + ra: coordinates.equatorial.ra, + dec: coordinates.equatorial.dec, label: object.catalogs.designation?.name || `PGC ${object.pgc}`, id: object.pgc, }, @@ -118,14 +119,23 @@ function skyViewForSources(sources: SkySource[]): SkyView | null { return { ra, dec, fov }; } +function isNumericCell( + value: React.ReactElement | string | number, +): value is number { + return Number.isFinite(value); +} + function resultTableColumns(): Column[] { return [ { name: "", width: "fit" }, { name: "PGC", - renderCell: (value: React.ReactElement | string | number) => ( - {value} - ), + renderCell: (value: React.ReactElement | string | number) => + isNumericCell(value) ? ( + {value} + ) : ( + value + ), }, { name: "Name" }, { name: "Type" }, @@ -133,12 +143,12 @@ function resultTableColumns(): Column[] { { name: "RA", renderCell: (value: React.ReactElement | string | number) => - typeof value === "number" ? : value, + isNumericCell(value) ? : value, }, { name: "Dec", renderCell: (value: React.ReactElement | string | number) => - typeof value === "number" ? : value, + isNumericCell(value) ? : value, }, ]; } @@ -148,9 +158,8 @@ function objectsToTableData( onLocate: (ra: number, dec: number) => void, ) { return objects.map((object) => { - const equatorial = object.catalogs.coordinates?.equatorial; - const hasCoords = - equatorial?.ra !== undefined && equatorial?.dec !== undefined; + const coordinates = object.catalogs.coordinates; + const hasCoords = coordinates !== undefined && coordinates !== null; return { "": hasCoords ? ( @@ -160,7 +169,7 @@ function objectsToTableData( hoverText="Locate" onClick={(event) => { event.stopPropagation(); - onLocate(equatorial.ra, equatorial.dec); + onLocate(coordinates.equatorial.ra, coordinates.equatorial.dec); }} > @@ -172,11 +181,11 @@ function objectsToTableData( Name: object.catalogs.designation?.name || "N/A", Type: object.catalogs.nature?.type_name || "N/A", Velocity: - object.catalogs.velocity?.heliocentric?.v !== undefined + object.catalogs.velocity?.heliocentric !== undefined ? `${object.catalogs.velocity.heliocentric.v.toFixed(0)} km/s` : "N/A", - RA: hasCoords ? equatorial.ra : "N/A", - Dec: hasCoords ? equatorial.dec : "N/A", + RA: hasCoords ? coordinates.equatorial.ra : "N/A", + Dec: hasCoords ? coordinates.equatorial.dec : "N/A", }; }); } @@ -273,10 +282,9 @@ async function fetchSearchType( }, }); - if (response.error || !response.data) { - const err = response.error; + if (response.error) { throw new Error( - `Error during ${type.title} query: ${typeof err === "object" ? JSON.stringify(err) : err}`, + `Error during ${type.title} query: ${describeUnknownError(response.error)}`, ); } @@ -319,6 +327,42 @@ async function fetcher( return { sections }; } +interface SearchPageContentProps { + results: MultiSearchResults | null; + loading: boolean; + error: string | null; + query: string; + page: number; + pageSize: number; + navigate: NavigateFunction; +} + +function SearchPageContent({ + results, + loading, + error, + query, + page, + pageSize, + navigate, +}: SearchPageContentProps): ReactElement { + if (loading) return ; + if (error) return ; + if (results) { + return ( + + ); + } + + return ; +} + export function SearchResultsPage(): ReactElement { const [searchParams] = useSearchParams(); const navigate = useNavigate(); @@ -339,24 +383,6 @@ export function SearchResultsPage(): ReactElement { [query, page, pageSize], ); - function Content(): ReactElement { - if (loading) return ; - if (error) return ; - if (results) { - return ( - - ); - } - - return ; - } - return ( <> - + ); } diff --git a/bun.lock b/bun.lock index dbf5015..39c3753 100644 --- a/bun.lock +++ b/bun.lock @@ -8,13 +8,12 @@ "serve-handler": "^6.1.6", }, "devDependencies": { - "@eslint/js": "^10.0.1", "@hey-api/openapi-ts": "^0.99.0", - "eslint": "^10.8.1", - "eslint-config-prettier": "^10.1.8", - "jiti": "^2.7.0", + "@oxlint/plugins": "1.79.0", + "oxlint": "1.79.0", + "oxlint-plugin-anti-slop": "git+https://github.com/dmmulroy/anti-slop.git", + "oxlint-tsgolint": "7.0.2001", "prettier": "3.9.6", - "typescript-eslint": "^8.66.0", }, }, "apps/admin": { @@ -102,22 +101,6 @@ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="], - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - - "@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], - - "@eslint/config-helpers": ["@eslint/config-helpers@0.7.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw=="], - - "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], - - "@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], - - "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="], - "@floating-ui/core": ["@floating-ui/core@1.7.4", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="], "@floating-ui/dom": ["@floating-ui/dom@1.8.0", "", { "dependencies": { "@floating-ui/core": "^1.8.0", "@floating-ui/utils": "^0.2.12" } }, "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg=="], @@ -140,16 +123,6 @@ "@hey-api/types": ["@hey-api/types@0.1.4", "", {}, "sha512-thWfawrDIP7wSI9ioT13I5soaaqB5vAPIiZmgD8PbeEVKNrkonc0N/Sjj97ezl7oQgusZmaNphGdMKipPO6IBg=="], - "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], - - "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], - - "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - "@hyperleda/admin": ["@hyperleda/admin@workspace:apps/admin"], "@hyperleda/lib": ["@hyperleda/lib@workspace:packages/lib"], @@ -220,6 +193,58 @@ "@oxc-project/types": ["@oxc-project/types@0.144.0", "", {}, "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg=="], + "@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@7.0.2001", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CUJEdbSZ54+Xy9OXqOhWLTKZKV0BBiV7C2i/ygyVmXtkUNXx5YCzN8DpSSshTAKktoL7S+tnQ/ftFG/i7X896w=="], + + "@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@7.0.2001", "", { "os": "darwin", "cpu": "x64" }, "sha512-pXfBb5BqONCcgrXQNUZWXgiYmRSWJzd97S8i41VVOh6ut0tyo+cJ5FKFpczDHxiVNfj/3e7c9B4MtztNdpIVCw=="], + + "@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@7.0.2001", "", { "os": "linux", "cpu": "arm64" }, "sha512-roP7zujb/QDPzDwEKsFFpzNHHy91/Y7oX9vQXk78ekyZtcQj1QXDIMH33gjDdHBfRl4K9pZ36xhRgrP4Zr+R8A=="], + + "@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@7.0.2001", "", { "os": "linux", "cpu": "x64" }, "sha512-UDezNqdECVmngu2TPnjaS1YoAmcTaBoI5lV9vk3VahBxoi+I5r9k3iJTT7qZoYWOXTD/7T7bNcwRgrocR6BscQ=="], + + "@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@7.0.2001", "", { "os": "win32", "cpu": "arm64" }, "sha512-uJZhqB6pdXLuN+AD1F5082byyQti/NPmJA77GtcFlmT2HzRelqbNls3SaIqxpjdFgvSBF9g0yOKGBkGFg7kX8Q=="], + + "@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@7.0.2001", "", { "os": "win32", "cpu": "x64" }, "sha512-FkDRm8hx9OwzGQqyWG1tO5QrTLRApff9DzSgpz9QZau37BR8d1VYKOxMLGf6shPZntJFoTwIIJYT68VndYDCog=="], + + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.79.0", "", { "os": "android", "cpu": "arm" }, "sha512-TebFaaMklO/RXzTv7PucaCq9l3X6D1gA+C8H6K4njtjFOV+zWE9MKLpulcJZN9bzytbUbQIY0mZuz12nQ5Kv4Q=="], + + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.79.0", "", { "os": "android", "cpu": "arm64" }, "sha512-KqqnOtAVgNsPPF0YSodkFZA1O80jcKoCZCTu3bgsszxA+MrMP9TLzfXitKjEj1FmrPprKDMdRDMmY3weESO9sg=="], + + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.79.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BVC2nsMzqQzRDPc5RhixkZ+m1p7iH4bxRRvqkbwDXX0PlQKm1BPy8J8cRjnAFafOq2QzI+BfO3vE8w2GZ3CBag=="], + + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.79.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-p6Lm+snmhGuLKL1+CpCV8L6ijkE/qJzK2H2jG9+eKJT0n31RbY4FLsdhexekgP3bLpw4Kgde+9DZuDZQ4yIInA=="], + + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.79.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-qDMm0dXZnoHyRqSL4N4xUq82T4sqK5cbKSjvd/dF/YbMUXc2R1wEPf+vmA5S0qUmi0nwXfNbjXBtZaIqzQLIMg=="], + + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.79.0", "", { "os": "linux", "cpu": "arm" }, "sha512-2od7s0nuKPzqyUZAWk9KkCyGg7eI9dwFPZg+20lB15fKFkVZ0c9ZFxqPfiBAyDTlTkh9stPI0t+JlPCqMbItVA=="], + + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.79.0", "", { "os": "linux", "cpu": "arm" }, "sha512-ZOQUjkzDnvlhSE3+tWC3YXx94MMl+sYMlwH+u1+YGApGHOJP/YAc8ZBRFOXZ6eOBmxtXAWuS/fBcdZr8qqNO1A=="], + + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.79.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-lu158FR4nGqGeRS3BQvtG85wRgU/Fy4MD5Cxp1hzJXizGiLo6u2742wJSCDKh8cFcZntvX7fcxlq4mMmfryH1g=="], + + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.79.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-mbpKQeE2aflTjddaHK7MP8KP/OFbUM++lt5M635ENM8IyIdK0jm2t9pb+2v9mVVIvhF6TqA4l7F79Pll1mi+uw=="], + + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.79.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WpGNua7gaxaHnpSDeog2ji8IDHn/QLPl9LPzwkR/FvVv58vT5BcXjRXnU+wbu3N75cpeha8CdC7ho/U2OIsB4g=="], + + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.79.0", "", { "os": "linux", "cpu": "none" }, "sha512-tK1E93A5LVzISg4ngpKJnfTs7EqtIUceGI7MQ4GyDjJiLi8wPCkEyKlj2xkyKWZ1yzkDJyLHTBJ5/iFWRdnJvg=="], + + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.79.0", "", { "os": "linux", "cpu": "none" }, "sha512-qhQvUIrngXivA2A9pQ+xPCychztn/5qUv7yS3gDwXv3w7Rag+eTeeXWmRyx+t7XsW5x6LuY/8AsTq36UgFIblg=="], + + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.79.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-sv6AaVgU/eE6u+6WFiQVDcPPwTxP6IJMSB9k701W2r/r6Tx465e8vPvVyRxquNH4Vy6KwRNu90mVbxXJN8+5gg=="], + + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.79.0", "", { "os": "linux", "cpu": "x64" }, "sha512-iFZL02deziHslb3jEX9KdqlAkYoo4fGyotchKDzdfK1f5mxlIBeiQeHhvK3iFpuEJSB4ma/qeFn9oxPiwnhUPQ=="], + + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.79.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3DtZR2raqObnh7wXZoFYFd0Fw7skBvcb3f7A+/lkEiDuh8hrE6vv9b/62Qxao1a9/OeHLw/FcXlXzgsW9wTRFg=="], + + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.79.0", "", { "os": "none", "cpu": "arm64" }, "sha512-Oatt4GuA1WJkqzk2ozx4HrWROOi7opV3AKDw/U8qDIqeTqzsjn5K2x3REJMNjU3/KU/Bkq96Zi3CknaiDTaC/Q=="], + + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.79.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-NAgZr9Qp8nIA9rpo0JEvwiabTF/2UVqBNnupBG9X4kxXcQoScJUTi+qHhvabb9s/thgj5wQ4XcIaJvb+ZMgoKw=="], + + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.79.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-+KyXjIvcpaXmWW/j9NNY5yWjrIVxaX18VyIheQy3jwc2GSYgpCr7MGI/HxIGQ/shAL5IWEKbhsqoMpAO5Stiog=="], + + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.79.0", "", { "os": "win32", "cpu": "x64" }, "sha512-mEelcCMMBS57sIXh2veGMNy+pQwuGtcMxHxGIZWQ5Ba9pJ5jCCUFOZB9E2JhBaxGsURe+WGe0zJp4RVre52gpQ=="], + + "@oxlint/plugins": ["@oxlint/plugins@1.79.0", "", {}, "sha512-S0uyoxakDINJ4DPgqxGlEEvrdSMeQb7Z2lKVjxoY2gwsbZbfg2Xr8Klfeo5ZeraHmmdBCELFUHkSe6KEmBpMvg=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA=="], "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ=="], @@ -322,8 +347,6 @@ "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], - "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], - "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -344,26 +367,6 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.67.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/type-utils": "8.67.0", "@typescript-eslint/utils": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.67.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.67.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.67.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.67.0", "@typescript-eslint/types": "^8.67.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.67.0", "", { "dependencies": { "@typescript-eslint/types": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0" } }, "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.67.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.67.0", "", { "dependencies": { "@typescript-eslint/types": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0", "@typescript-eslint/utils": "8.67.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.67.0", "", {}, "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.67.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.67.0", "@typescript-eslint/tsconfig-utils": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.67.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.67.0", "", { "dependencies": { "@typescript-eslint/types": "8.67.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA=="], - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.3", "", {}, "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg=="], "@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@4.3.3", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1", "@swc/core": "^1.15.46" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7 || ^8" } }, "sha512-bti8ZAcvz4Lh6/e4Uk2k3aa1TiUXbbMsahuqOHvd3MveFTkKDZOA6wQVkpj7J/+tepX/wGfe+lsGh/t24HTXMQ=="], @@ -382,14 +385,8 @@ "@vitest/utils": ["@vitest/utils@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA=="], - "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], - "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -404,9 +401,9 @@ "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], + "brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], @@ -466,8 +463,6 @@ "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], @@ -506,54 +501,20 @@ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@10.8.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ=="], - - "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], - - "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - - "espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], - "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], - "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="], "exsolve": ["exsolve@1.1.1", "", {}, "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g=="], "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - - "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="], - "flowbite-react": ["flowbite-react@0.12.17", "", { "dependencies": { "@floating-ui/core": "1.7.4", "@floating-ui/react": "0.27.17", "@iarna/toml": "2.2.5", "chokidar": "4.0.3", "comment-json": "4.5.1", "debounce": "3.0.0", "deepmerge-ts": "7.1.5", "klona": "2.0.6", "magic-string": "0.30.21", "oxc-parser": "0.112.0", "package-manager-detector": "1.6.0", "tailwind-merge-v2": "npm:tailwind-merge@2.6.1", "tailwind-merge-v3": "npm:tailwind-merge@3.4.0" }, "peerDependencies": { "react": "^18 || ^19", "react-dom": "^18 || ^19", "tailwindcss": "^3 || ^4" }, "bin": { "flowbite-react": "dist/cli/bin.js" } }, "sha512-En0zhGePac4hFWdikoyor0dQlUM8PbLPvGvFGovssDcw82pdjp9XLg3+bAaiy+83/7vEQY6Ajjnzbrsw4ru/Cg=="], "follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], @@ -572,8 +533,6 @@ "giget": ["giget@3.3.1", "", { "bin": { "giget": "dist/cli.mjs" } }, "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg=="], - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], @@ -592,10 +551,6 @@ "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], @@ -606,10 +561,6 @@ "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="], @@ -626,18 +577,8 @@ "js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="], - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], @@ -662,8 +603,6 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -734,7 +673,7 @@ "mime-types": ["mime-types@2.1.18", "", { "dependencies": { "mime-db": "~1.33.0" } }, "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ=="], - "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "monaco-editor": ["monaco-editor@0.56.0", "", { "dependencies": { "dompurify": "3.4.8", "marked": "14.0.0" } }, "sha512-sXboRm3BeBeLm938eaiyLMe0OxzfXIlZvbv4ir/jVgQy1zDhWjgmny0WoN45fuDKhCCQsYMbBJrv/A6jd8aCUg=="], @@ -742,28 +681,24 @@ "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], "ohash": ["ohash@2.0.12", "", {}, "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw=="], "open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="], - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - "oxc-parser": ["oxc-parser@0.112.0", "", { "dependencies": { "@oxc-project/types": "^0.112.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.112.0", "@oxc-parser/binding-android-arm64": "0.112.0", "@oxc-parser/binding-darwin-arm64": "0.112.0", "@oxc-parser/binding-darwin-x64": "0.112.0", "@oxc-parser/binding-freebsd-x64": "0.112.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.112.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.112.0", "@oxc-parser/binding-linux-arm64-gnu": "0.112.0", "@oxc-parser/binding-linux-arm64-musl": "0.112.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.112.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.112.0", "@oxc-parser/binding-linux-riscv64-musl": "0.112.0", "@oxc-parser/binding-linux-s390x-gnu": "0.112.0", "@oxc-parser/binding-linux-x64-gnu": "0.112.0", "@oxc-parser/binding-linux-x64-musl": "0.112.0", "@oxc-parser/binding-openharmony-arm64": "0.112.0", "@oxc-parser/binding-wasm32-wasi": "0.112.0", "@oxc-parser/binding-win32-arm64-msvc": "0.112.0", "@oxc-parser/binding-win32-ia32-msvc": "0.112.0", "@oxc-parser/binding-win32-x64-msvc": "0.112.0" } }, "sha512-7rQ3QdJwobMQLMZwQaPuPYMEF2fDRZwf51lZ//V+bA37nejjKW5ifMHbbCwvA889Y4RLhT+/wLJpPRhAoBaZYw=="], - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + "oxlint": ["oxlint@1.79.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.79.0", "@oxlint/binding-android-arm64": "1.79.0", "@oxlint/binding-darwin-arm64": "1.79.0", "@oxlint/binding-darwin-x64": "1.79.0", "@oxlint/binding-freebsd-x64": "1.79.0", "@oxlint/binding-linux-arm-gnueabihf": "1.79.0", "@oxlint/binding-linux-arm-musleabihf": "1.79.0", "@oxlint/binding-linux-arm64-gnu": "1.79.0", "@oxlint/binding-linux-arm64-musl": "1.79.0", "@oxlint/binding-linux-ppc64-gnu": "1.79.0", "@oxlint/binding-linux-riscv64-gnu": "1.79.0", "@oxlint/binding-linux-riscv64-musl": "1.79.0", "@oxlint/binding-linux-s390x-gnu": "1.79.0", "@oxlint/binding-linux-x64-gnu": "1.79.0", "@oxlint/binding-linux-x64-musl": "1.79.0", "@oxlint/binding-openharmony-arm64": "1.79.0", "@oxlint/binding-win32-arm64-msvc": "1.79.0", "@oxlint/binding-win32-ia32-msvc": "1.79.0", "@oxlint/binding-win32-x64-msvc": "1.79.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2001", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-hVJ9hq9m2unPS+Of4eJJgCPdIeCC+3DHEUX3tkmrPJr3OK2hz7PhXwgC+ZP71ZcYu8cCDEtQrqLxWNvxBppBVg=="], + + "oxlint-plugin-anti-slop": ["oxlint-plugin-anti-slop@github:dmmulroy/anti-slop#6d53855", { "dependencies": { "@oxlint/plugins": "1.78.0" } }, "dmmulroy-anti-slop-6d53855", "sha512-xcf/SwTxkeYVH6G+4CY3ZCPTXX0sznV+pF6tv3wneo97enUo5FTPyj3smo0Bu6613nNFSt8X2D2yyGfCcQxG6g=="], - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "oxlint-tsgolint": ["oxlint-tsgolint@7.0.2001", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "7.0.2001", "@oxlint-tsgolint/darwin-x64": "7.0.2001", "@oxlint-tsgolint/linux-arm64": "7.0.2001", "@oxlint-tsgolint/linux-x64": "7.0.2001", "@oxlint-tsgolint/win32-arm64": "7.0.2001", "@oxlint-tsgolint/win32-x64": "7.0.2001" }, "bin": { "tsgolint": "./bin/tsgolint.js" } }, "sha512-KjK/XLcXr1DSyonKhsuFqJRiuKqcyG9j3LJ8nkOsrLzGvodBPqzHOKauy10asLMDI0sUpvb+1sxlzff3udZvfg=="], "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - "path-is-inside": ["path-is-inside@1.0.2", "", {}, "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -786,16 +721,12 @@ "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - "prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="], "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "range-parser": ["range-parser@1.2.0", "", {}, "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A=="], "rc9": ["rc9@3.0.1", "", { "dependencies": { "defu": "^6.1.6", "destr": "^2.0.5" } }, "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ=="], @@ -876,16 +807,10 @@ "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], - "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], - "typescript-eslint": ["typescript-eslint@8.67.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.67.0", "@typescript-eslint/parser": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0", "@typescript-eslint/utils": "8.67.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg=="], - "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], @@ -900,8 +825,6 @@ "uplot": ["uplot@1.6.32", "", {}, "sha512-KIMVnG68zvu5XXUbC4LQEPnhwOxBuLyW1AHtpm6IKTXImkbLgkMy+jabjLgSLMasNuGGzQm/ep3tOkyTxpiQIw=="], - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], @@ -916,16 +839,10 @@ "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@floating-ui/dom/@floating-ui/core": ["@floating-ui/core@1.8.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.12" } }, "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ=="], "@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -942,19 +859,15 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="], - - "@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "oxc-parser/@oxc-project/types": ["@oxc-project/types@0.112.0", "", {}, "sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ=="], - "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "oxlint-plugin-anti-slop/@oxlint/plugins": ["@oxlint/plugins@1.78.0", "", {}, "sha512-Ypt8KeRYw+4jUtlPirfcHWMrn5ms12VrrFPD+Mds477/7tJxG1Kcz2Yrg2nVcTQEUx/GdlhS+BUg1kmxNm04Ug=="], - "serve-handler/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], @@ -981,9 +894,5 @@ "c12/chokidar/readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "serve-handler/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], - - "serve-handler/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], } } diff --git a/eslint.config.ts b/eslint.config.ts deleted file mode 100644 index 2f9fff8..0000000 --- a/eslint.config.ts +++ /dev/null @@ -1,76 +0,0 @@ -import eslint from "@eslint/js"; -import tseslint from "typescript-eslint"; -import eslintConfigPrettier from "eslint-config-prettier/flat"; - -export default tseslint.config( - { - ignores: [ - "**/dist/**", - "packages/lib/src/clients/**", - "apps/admin/src/clients/admin/**", - ], - }, - eslintConfigPrettier, - { - extends: [eslint.configs.recommended, tseslint.configs.recommended], - languageOptions: { - parserOptions: { - tsconfigRootDir: import.meta.dirname, - }, - }, - rules: { - "@typescript-eslint/array-type": "error", - "array-callback-return": "error", - "no-await-in-loop": "error", - "no-constructor-return": "error", - "no-inner-declarations": "error", - "no-promise-executor-return": "error", - "no-self-compare": "error", - "no-template-curly-in-string": "error", - "no-unassigned-vars": "error", - "no-unreachable-loop": "error", - "no-use-before-define": "error", - "no-useless-assignment": "error", - "require-atomic-updates": "error", - - "arrow-body-style": "error", - "block-scoped-var": "error", - camelcase: ["error", { properties: "never" }], - "consistent-return": "error", - "default-case": "error", - "default-case-last": "error", - "default-param-last": "error", - eqeqeq: "error", - "func-name-matching": "error", - "func-style": ["error", "declaration"], - "init-declarations": ["error", "always"], - "no-array-constructor": "error", - "no-caller": "error", - "no-delete-var": "error", - "no-else-return": "error", - "no-empty": "error", - "no-invalid-this": "error", - "no-label-var": "error", - "no-lone-blocks": "error", - "no-lonely-if": "error", - "no-loop-func": "error", - "no-octal": "error", - "no-octal-escape": "error", - "no-return-assign": "error", - "no-sequences": "error", - "no-throw-literal": "error", - "no-unneeded-ternary": "error", - "no-useless-rename": "error", - "no-useless-return": "error", - "no-var": "error", - "prefer-const": "error", - "require-await": "error", - }, - }, - { - files: ["configs/**"], - languageOptions: { - globals: { window: "writable" }, - }, - }, -); diff --git a/makefile b/makefile index 97ab3f6..c489048 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,6 @@ check: @output=$$(bun --silent run prettier --check apps/web/src apps/admin/src packages/lib/src 2>&1) || { echo "$$output"; exit 1; } - @output=$$(bun --silent run eslint . 2>&1) || { echo "$$output"; exit 1; } + @output=$$(bun --silent run oxlint . 2>&1) || { echo "$$output"; exit 1; } @bun run test:web @bun run test:admin @bun run build:web @@ -8,7 +8,7 @@ check: fix: @output=$$(bun --silent run prettier --write apps/web/src apps/admin/src packages/lib/src 2>&1) || { echo "$$output"; exit 1; } - @output=$$(bun --silent run eslint --fix . 2>&1) || { echo "$$output"; exit 1; } + @output=$$(bun --silent run oxlint --fix . 2>&1) || { echo "$$output"; exit 1; } run-web: bun run dev:web diff --git a/package.json b/package.json index 8cee0f6..91854a1 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "dev:admin": "bun run --cwd apps/admin dev", "build:web": "bun run --cwd apps/web build", "build:admin": "bun run --cwd apps/admin build", - "lint": "eslint .", - "eslint": "eslint", + "lint": "bun scripts/run-oxlint.mjs .", + "oxlint": "bun scripts/run-oxlint.mjs", "test:web": "bun run --cwd apps/web test", "test:admin": "bun run --cwd apps/admin test", "start": "node server.mjs", @@ -19,12 +19,11 @@ "serve-handler": "^6.1.6" }, "devDependencies": { - "@eslint/js": "^10.0.1", "@hey-api/openapi-ts": "^0.99.0", - "eslint": "^10.8.1", - "eslint-config-prettier": "^10.1.8", - "jiti": "^2.7.0", - "prettier": "3.9.6", - "typescript-eslint": "^8.66.0" + "@oxlint/plugins": "1.79.0", + "oxlint": "1.79.0", + "oxlint-plugin-anti-slop": "git+https://github.com/dmmulroy/anti-slop.git", + "oxlint-tsgolint": "7.0.2001", + "prettier": "3.9.6" } } diff --git a/packages/lib/src/astronomy/sexagesimal.ts b/packages/lib/src/astronomy/sexagesimal.ts index 3318275..144d574 100644 --- a/packages/lib/src/astronomy/sexagesimal.ts +++ b/packages/lib/src/astronomy/sexagesimal.ts @@ -2,11 +2,20 @@ export function pad2(value: number): string { return String(value).padStart(2, "0"); } -export function decomposeRa(degrees: number): { +export interface RaComponents { h: number; m: number; s: number; -} { +} + +export interface DecComponents { + sign: string; + d: number; + m: number; + s: number; +} + +export function decomposeRa(degrees: number): RaComponents { const totalSeconds = degrees * 240; return { h: Math.floor(totalSeconds / 3600), @@ -15,12 +24,7 @@ export function decomposeRa(degrees: number): { }; } -export function decomposeDec(degrees: number): { - sign: string; - d: number; - m: number; - s: number; -} { +export function decomposeDec(degrees: number): DecComponents { const sign = degrees < 0 ? "-" : "+"; const absDec = Math.abs(degrees); const d = Math.floor(absDec); diff --git a/packages/lib/src/config.ts b/packages/lib/src/config.ts index 1958515..2f179da 100644 --- a/packages/lib/src/config.ts +++ b/packages/lib/src/config.ts @@ -10,7 +10,7 @@ declare global { } function getConfig(): AppConfig { - if (typeof window === "undefined") { + if (!("window" in globalThis)) { throw new Error( "App configuration is required. Please set window.__APP_CONFIG__", ); diff --git a/packages/lib/src/hooks/useAnchoredElement.ts b/packages/lib/src/hooks/useAnchoredElement.ts index b9ee719..4da5475 100644 --- a/packages/lib/src/hooks/useAnchoredElement.ts +++ b/packages/lib/src/hooks/useAnchoredElement.ts @@ -3,10 +3,12 @@ import { useLocation } from "react-router-dom"; const HIGHLIGHT_DURATION_MS = 2000; -export function useAnchoredElement(id: string): { +interface AnchoredElementState { ref: RefObject; highlighted: boolean; -} { +} + +export function useAnchoredElement(id: string): AnchoredElementState { const ref = useRef(null); const location = useLocation(); const [highlighted, setHighlighted] = useState(false); diff --git a/packages/lib/src/hooks/useDataFetching.ts b/packages/lib/src/hooks/useDataFetching.ts index a564571..c26069f 100644 --- a/packages/lib/src/hooks/useDataFetching.ts +++ b/packages/lib/src/hooks/useDataFetching.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; interface UseDataFetchingResult { data: T | null; @@ -13,23 +13,26 @@ export function useDataFetching( const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const fetcherRef = useRef(fetcher); + fetcherRef.current = fetcher; + const dependencyKey = JSON.stringify(dependencies); useEffect(() => { setLoading(true); setError(null); async function fetchData(): Promise { try { - const result = await fetcher(); + const result = await fetcherRef.current(); setData(result); } catch (err) { - setError(`${err}`); + setError(err instanceof Error ? err.message : String(err)); } finally { setLoading(false); } } - fetchData(); - }, dependencies); + void fetchData(); + }, [dependencyKey]); return { data, loading, error }; } diff --git a/packages/lib/src/tap.ts b/packages/lib/src/tap.ts index 1f94ab8..7fa91a0 100644 --- a/packages/lib/src/tap.ts +++ b/packages/lib/src/tap.ts @@ -11,17 +11,56 @@ export interface TapTableColumn { name: string; } +export interface TapTableData { + columns: TapTableColumn[]; + rows: Record[]; +} + +interface ApiErrorPayload { + detail?: ValidationError[]; +} + export const DEFAULT_SQL_EXAMPLE = "SELECT * FROM layer2.designations WHERE pgc = 67872"; -export function formatApiError(error: unknown): string { - const detail = (error as { detail?: ValidationError[] }).detail; - if (detail?.length) { - return detail.map((e) => e.msg).join(", "); +export function formatCaughtError(cause: unknown): string { + return cause instanceof Error ? cause.message : String(cause); +} + +export function describeUnknownError( + error: string | ApiErrorPayload | ValidationError[] | null | undefined, +): string { + if (error === null || error === undefined) { + return String(error); + } + if (Object(error) !== error) { + return String(error); } return JSON.stringify(error); } +export function formatApiError( + error: string | ApiErrorPayload | ValidationError[] | null | undefined, +): string { + if (error === null || error === undefined) { + return "Unknown error"; + } + if (Array.isArray(error)) { + return error.length + ? error.map((entry) => entry.msg).join(", ") + : JSON.stringify(error); + } + if (Object(error) !== error) { + return String(error); + } + // SAFETY: Remaining errors are API payloads after array and primitive checks. + const payload = error as ApiErrorPayload; + if (payload.detail?.length) { + return payload.detail.map((entry) => entry.msg).join(", "); + } + return JSON.stringify(payload); +} + export async function executeSqlQuery(sql: string): Promise { const response = await tapSync({ client: backendClient, @@ -30,33 +69,37 @@ export async function executeSqlQuery(sql: string): Promise { if (response.error) { throw new Error(formatApiError(response.error)); } - if (!response.data?.data) { - throw new Error("No data received from server"); - } return response.data.data; } -export function cellValue(value: unknown): TapCellValue { +type TapJsonCell = string | number | boolean | null; + +function isTapNumber(value: string | number | boolean): value is number { + return Number.isFinite(value); +} + +export function cellValue( + value: string | number | boolean | null | undefined, +): TapCellValue { if (value === null || value === undefined) { return "—"; } - if (typeof value === "number") { + if (isTapNumber(value)) { return value; } return String(value); } -export function syncPayloadToTable(payload: TapSyncResponse): { - columns: TapTableColumn[]; - rows: Record[]; -} { +export function syncPayloadToTable(payload: TapSyncResponse): TapTableData { const syncTable = payload.resource.table; const syncColumns = syncTable.columns; const columns: TapTableColumn[] = syncColumns.map((c) => ({ name: c.name })); - const rows = (syncTable.data ?? []).map((row) => { + const rows = syncTable.data.map((row) => { const out: Record = {}; for (let i = 0; i < syncColumns.length; i++) { - out[syncColumns[i].name] = cellValue(row[i]); + // SAFETY: TAP sync rows contain JSON scalar cell values. + const raw = row[i] as TapJsonCell | undefined; + out[syncColumns[i].name] = cellValue(raw); } return out; }); diff --git a/packages/lib/src/ui/Aladin.tsx b/packages/lib/src/ui/Aladin.tsx index 244e5c6..4390f48 100644 --- a/packages/lib/src/ui/Aladin.tsx +++ b/packages/lib/src/ui/Aladin.tsx @@ -35,6 +35,7 @@ const ALADIN_SURVEYS = [ ] as const; const DEFAULT_ALADIN_SURVEY = ALADIN_SURVEYS[0].survey; +const ALADIN_MARKER_STYLE = "shape" as const; const SOURCE_SIZE = 8; const LABEL_FONT = "14px sans-serif"; @@ -198,7 +199,7 @@ export const AladinViewer = forwardRef( }, [survey]); useEffect(() => { - if (!aladinDivRef.current || !window.A) { + if (!aladinDivRef.current) { return undefined; } @@ -231,21 +232,23 @@ export const AladinViewer = forwardRef( onSourceClickRef.current?.(id); }); - if (additionalSources && additionalSources.length > 0) { + // SAFETY: additionalSourcesKey is JSON.stringify of AdditionalSource[]. + const sources = JSON.parse(additionalSourcesKey) as AdditionalSource[]; + if (sources.length > 0) { const nameCatalog = window.A.catalog({ - shape: drawSourceWithLabel, + [ALADIN_MARKER_STYLE]: drawSourceWithLabel, color: "black", displayLabel: false, sourceSize: SOURCE_SIZE, }); const descrCatalog = window.A.catalog({ color: "black", - shape: "cross", + [ALADIN_MARKER_STYLE]: "cross", }); aladin.addCatalog(nameCatalog); aladin.addCatalog(descrCatalog); - additionalSources.forEach((source) => { + sources.forEach((source) => { const data: AladinSourceData = { name: source.label, id: source.id, @@ -302,6 +305,17 @@ export const AladinViewer = forwardRef( }, ); +type AladinMarkerRenderer = ( + source: AladinCanvasSource, + ctx: CanvasRenderingContext2D, +) => void; + +type AladinCatalogOptions = { + displayLabel?: boolean; + sourceSize?: number; + color?: string; +} & Partial>; + interface AladinCatalog { addSources: (sources: AladinSource | AladinSource[]) => void; } @@ -328,17 +342,7 @@ declare global { showCooGridControl?: boolean; }, ) => AladinInstance; - catalog: (options?: { - displayLabel?: boolean; - sourceSize?: number; - shape?: - | string - | (( - source: AladinCanvasSource, - ctx: CanvasRenderingContext2D, - ) => void); - color?: string; - }) => AladinCatalog; + catalog: (options?: AladinCatalogOptions) => AladinCatalog; source: ( ra: number, dec: number, diff --git a/packages/lib/src/ui/CardActionsMenu.tsx b/packages/lib/src/ui/CardActionsMenu.tsx index 0587b9e..fc95cd6 100644 --- a/packages/lib/src/ui/CardActionsMenu.tsx +++ b/packages/lib/src/ui/CardActionsMenu.tsx @@ -67,6 +67,7 @@ export function CardActionsMenu({ function handlePointerDown(event: MouseEvent) { if ( containerRef.current && + // SAFETY: `contains` expects a DOM Node; event targets inside the document are Nodes. !containerRef.current.contains(event.target as Node) ) { setOpen(false); diff --git a/packages/lib/src/ui/CardAnchorLink.tsx b/packages/lib/src/ui/CardAnchorLink.tsx index 4873b5b..669ac9a 100644 --- a/packages/lib/src/ui/CardAnchorLink.tsx +++ b/packages/lib/src/ui/CardAnchorLink.tsx @@ -11,7 +11,7 @@ export function CardAnchorLink({ function handleClick(event: MouseEvent): void { event.preventDefault(); - navigate({ hash: anchorId }, { replace: true }); + void navigate({ hash: anchorId }, { replace: true }); } return ( diff --git a/packages/lib/src/ui/CatalogSqlPanel.tsx b/packages/lib/src/ui/CatalogSqlPanel.tsx index f9f8d60..bf22362 100644 --- a/packages/lib/src/ui/CatalogSqlPanel.tsx +++ b/packages/lib/src/ui/CatalogSqlPanel.tsx @@ -1,4 +1,11 @@ -import { FormEvent, ReactElement, useEffect, useRef, useState } from "react"; +import { + FormEvent, + ReactElement, + useCallback, + useEffect, + useRef, + useState, +} from "react"; import { useLocation } from "react-router-dom"; import type { TapSchemaEntry, @@ -10,7 +17,7 @@ import { SqlQueryEmbed } from "./SqlQueryEmbed"; import { Text } from "./Text"; function runQueryShortcutLabel(): string { - if (typeof navigator === "undefined") { + if (!("navigator" in globalThis)) { return "Ctrl+Enter"; } return /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? "⌘↵" : "Ctrl+Enter"; @@ -41,13 +48,16 @@ export function CatalogSqlPanel({ const location = useLocation(); const didAutoRun = useRef(false); - function triggerRun(trimmed: string): void { - setValidationError(null); - setExecutedSql(trimmed); - setRunId((id) => id + 1); - setLoading(true); - onQueryRun?.(trimmed); - } + const triggerRun = useCallback( + (trimmed: string): void => { + setValidationError(null); + setExecutedSql(trimmed); + setRunId((id) => id + 1); + setLoading(true); + onQueryRun?.(trimmed); + }, + [onQueryRun], + ); function runQuery(): void { if (loading) { @@ -81,7 +91,7 @@ export function CatalogSqlPanel({ } didAutoRun.current = true; triggerRun(sql.trim()); - }, [permalinkRunKey, sql]); + }, [permalinkRunKey, sql, triggerRun]); return (
diff --git a/packages/lib/src/ui/CommonTable.tsx b/packages/lib/src/ui/CommonTable.tsx index ceef6f9..ac09f60 100644 --- a/packages/lib/src/ui/CommonTable.tsx +++ b/packages/lib/src/ui/CommonTable.tsx @@ -51,10 +51,6 @@ export function CommonTable({ return column.renderCell(value); } - if (value === undefined || value === null) { - return
; - } - if (React.isValidElement(value)) { return value; } diff --git a/packages/lib/src/ui/Markdown.tsx b/packages/lib/src/ui/Markdown.tsx index fae2ab5..d564701 100644 --- a/packages/lib/src/ui/Markdown.tsx +++ b/packages/lib/src/ui/Markdown.tsx @@ -2,23 +2,37 @@ import { ReactElement, ReactNode } from "react"; import ReactMarkdown from "react-markdown"; import { Link } from "@hyperleda/lib/ui"; +function MarkdownLink({ + href, + children, +}: { + href?: string; + children?: ReactNode; +}): ReactElement { + const url = href ?? ""; + const external = /^https?:\/\//.test(url); + return ( + + {children} + + ); +} + +function MarkdownParagraph({ + children, +}: { + children?: ReactNode; +}): ReactElement { + return {children}; +} + export function Markdown({ children }: { children: string }): ReactElement { return (
{ - const url = href ?? ""; - const external = /^https?:\/\//.test(url); - return ( - - {linkChildren} - - ); - }, - p: ({ children: paragraphChildren }) => ( - {paragraphChildren as ReactNode} - ), + a: MarkdownLink, + p: MarkdownParagraph, }} > {children} diff --git a/packages/lib/src/ui/SqlQueryEmbed.tsx b/packages/lib/src/ui/SqlQueryEmbed.tsx index d062ab6..4c3ff1e 100644 --- a/packages/lib/src/ui/SqlQueryEmbed.tsx +++ b/packages/lib/src/ui/SqlQueryEmbed.tsx @@ -1,15 +1,12 @@ import React, { ReactElement, ReactNode, useEffect, useState } from "react"; import type { TapSyncResponse } from "@hyperleda/lib/clients/backend"; -import { executeSqlQuery, syncPayloadToTable } from "../tap"; +import { executeSqlQuery, formatCaughtError, syncPayloadToTable } from "../tap"; import { type CellPrimitive, type Column, CommonTable } from "./CommonTable"; import { Loading } from "./Loading"; import { Markdown } from "./Markdown"; import { Text } from "./Text"; function renderMarkdownCell(value: CellPrimitive): ReactNode { - if (value === undefined || value === null) { - return
; - } if (React.isValidElement(value)) { return value; } @@ -58,7 +55,7 @@ export function SqlQueryEmbed({ } } catch (runError) { if (!cancelled) { - setError(`${runError}`); + setError(formatCaughtError(runError)); } } finally { if (!cancelled) { diff --git a/packages/lib/src/ui/Text.tsx b/packages/lib/src/ui/Text.tsx index 27e7771..8de4d95 100644 --- a/packages/lib/src/ui/Text.tsx +++ b/packages/lib/src/ui/Text.tsx @@ -5,17 +5,17 @@ export type TextStyle = "header" | "info"; export type TextSize = "small" | "medium" | "large"; export type TextType = "code" | "normal"; -const headerBySize: Record = { +const headerBySize = { small: "text-sm font-medium text-primary leading-snug", medium: "text-2xl font-semibold text-primary leading-snug max-w-3xl", large: "text-3xl font-semibold text-primary leading-snug", -}; +} satisfies Record; -const infoBySize: Record = { +const infoBySize = { small: "text-xs text-muted leading-snug", medium: "text-sm text-muted leading-relaxed", large: "text-lg text-subtle", -}; +} satisfies Record; interface TextProps { style?: TextStyle; diff --git a/packages/lib/src/ui/useTheme.tsx b/packages/lib/src/ui/useTheme.tsx index 7e56ee7..28f312c 100644 --- a/packages/lib/src/ui/useTheme.tsx +++ b/packages/lib/src/ui/useTheme.tsx @@ -137,11 +137,11 @@ export function nextThemePreference(current: ThemePreference): ThemePreference { return themeCycle[(index + 1) % themeCycle.length]; } -const themeLabels: Record = { +const themeLabels = { system: "System", light: "Light", dark: "Dark", -}; +} satisfies Record; export function themePreferenceLabel(theme: ThemePreference): string { return themeLabels[theme]; diff --git a/scripts/run-oxlint.mjs b/scripts/run-oxlint.mjs new file mode 100644 index 0000000..94916ab --- /dev/null +++ b/scripts/run-oxlint.mjs @@ -0,0 +1,19 @@ +import { spawnSync } from "node:child_process"; +import { cpSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const repoRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); +const source = dirname(fileURLToPath(import.meta.resolve("oxlint-plugin-anti-slop"))); +const dest = join(repoRoot, ".oxlint-plugins", "anti-slop"); + +rmSync(dest, { recursive: true, force: true }); +mkdirSync(dirname(dest), { recursive: true }); +cpSync(source, dest, { recursive: true }); +writeFileSync(join(dest, "package.json"), JSON.stringify({ type: "module" })); + +const result = spawnSync(join(repoRoot, "node_modules", ".bin", "oxlint"), process.argv.slice(2), { + stdio: "inherit", +}); + +process.exit(result.status ?? 1); diff --git a/server.mjs b/server.mjs index 2c545dc..6afc403 100644 --- a/server.mjs +++ b/server.mjs @@ -13,13 +13,13 @@ function log(level, msg, fields = {}) { function headerValue(headers, name) { const value = headers[name]; - if (typeof value === "string") { - return value; + if (value === undefined) { + return undefined; } - if (Array.isArray(value) && value.length > 0) { - return value[0]; + if (Array.isArray(value)) { + return value.length > 0 ? value[0] : undefined; } - return undefined; + return value; } function normalizeIp(ip) {