diff --git a/AGENTS.md b/AGENTS.md index 76bb2eeed..1c3290c7d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,6 +58,7 @@ Read the relevant doc before working in that area: - `docs/agents/testing.md` — Vitest patterns, DbState, factories, backward-compat - `docs/agents/connectors.md` — Gremlin/openCypher/SPARQL query templates - `docs/agents/schema.md` — schema storage, discovery, Jotai atoms +- `docs/agents/browser-floor.md` — supported browsers and how build, types, and lint enforce them - `docs/agents/issue-tracker.md` — GitHub issue and PR conventions - `docs/agents/documentation.md` — writing user-facing docs (READMEs, guides, docs site) - `docs/agents/product.md` — product overview, supported databases, architecture diff --git a/REVIEW.md b/REVIEW.md index 74f82925b..620c8b536 100644 --- a/REVIEW.md +++ b/REVIEW.md @@ -12,7 +12,7 @@ Reserve Important for the cases below. Everything else is Nit at most. That incl - Values that reach a query outside a Query Fragment, or any other break of the rules in `docs/agents/connectors.md`. - A limit that doesn't bound the scan. - A change to one connector, or to shared query code, that changes behavior in the other query languages without saying why. -- **Runtime APIs newer than the browser floor in `packages/graph-explorer/.browserslistrc`,** such as `Error.isError` or Iterator helpers. The browser packages set `lib: ES2023` (graph-explorer adds `ES2024.String`, which is inside the floor), so `tsc` catches the rest of ES2024 and later APIs, but not ES2023 APIs that shipped after the floor, or DOM APIs, which `lib` doesn't version. +- **Runtime APIs newer than the browser floor in `packages/graph-explorer/.browserslistrc`,** such as `Error.isError` or Iterator helpers. `tsc` and oxlint catch most of these. Check by hand for what they miss, listed in `docs/agents/browser-floor.md`: DOM instance members, and built-in features that `lib` allows but the floor doesn't. - **Production code that imports a `devDependency`.** - **Work that grows with graph or schema size:** one query or subscription per item, quadratic loops, or copying passes over large collections. - **Tests that can't fail.** Name a plausible regression of the behavior the test's name promises, and prove the verdict with a mutation: inject that regression and run the test. A test that stays green is the finding, and the fix is to tighten it until the mutation turns it red. Recommend deletion only when no regression of that behavior could reach the test. Flag these: diff --git a/docs/agents/browser-floor.md b/docs/agents/browser-floor.md new file mode 100644 index 000000000..b60a945ef --- /dev/null +++ b/docs/agents/browser-floor.md @@ -0,0 +1,25 @@ +# Browser Floor + +Graph Explorer supports the browsers that are Baseline Widely available. `packages/graph-explorer/.browserslistrc` pins their minimum versions. Raise them on purpose, never silently through a dependency bump. + +Three layers enforce the floor. Each one catches what the others miss: + +- **Build**: `vite.config.ts` derives `build.target` from `.browserslistrc` through `buildTarget.ts`. Vite lowers JS syntax to that target, but it never polyfills a missing API. CSS follows the same floor because Vite's Lightning CSS minify compiles for `build.cssTarget`, which defaults to `build.target`. Tailwind's own optimize pass is off because its browser targets are fixed. +- **Types**: browser packages set `lib: ES2023`, so `tsc` rejects ES2024 and later built-ins, including instance methods such as Iterator helpers and `Set.prototype.union`. graph-explorer also adds `ES2024.String`, whose `isWellFormed` and `toWellFormed` are inside the floor. +- **Lint**: in the root `vite.config.ts` `lint` block, production code in `packages/graph-explorer/src` and `packages/shared/src` runs `baseline-js/use-baseline` with `available: "widely"`, along with a few `es-x` syntax rules. Tests are excluded because they run in Node. + +## What the lint covers + +- `baseline-js` flags static built-ins (`Error.isError`, `Promise.try`) and Web APIs that aren't Widely available. Its data comes bundled with the plugin version, so upgrading the plugin can move what it allows. +- The `es-x` rules flag syntax the other two layers let through: `using` declarations, RegExp duplicate named groups and modifiers, import attributes, and JSON modules. +- `ignoreFeatures` skips two features: + - `html-wrapper-methods`, which flags any `.bold` or `.anchor` property. + - `top-level-await`, which web-features lists as Safari 27 even though the floor's Safari already runs it and Vite builds it for the floor. + +## What nothing catches + +- Instance members on DOM types, such as ARIA reflection properties, `inert`, and `element.checkVisibility()`. Under oxlint, `baseline-js` has no type information, and `lib`'s `DOM` isn't versioned. +- Instance methods on built-ins that `lib` allows but the floor doesn't. The same missing type information hides them from the lint. +- ES2023 features outside the floor, such as symbols as `WeakMap` keys (Firefox 146). + +Check these by hand in review. A justified `oxlint-disable-next-line baseline-js/use-baseline -- ` is fine when the code feature-detects the API, when the lint misreads a feature that is Widely available, or when `.browserslistrc` already covers a feature that the plugin's bundled data doesn't yet count as Widely available. diff --git a/docs/agents/testing.md b/docs/agents/testing.md index 3d49a394d..671c5f335 100644 --- a/docs/agents/testing.md +++ b/docs/agents/testing.md @@ -82,7 +82,6 @@ Commands are in AGENTS.md. - **`vi.doMock` + dynamic `import()`**: call `vi.resetModules()` in the test's own `beforeEach` (not global — it's expensive). See any test that swaps a module impl between cases. - **Production behavior**: tests run `DEV=true`/`PROD=false`; override per-test with `vi.stubEnv("PROD", true)`. - **jsdom/happy-dom layout**: jsdom and happy-dom never lay out elements, so `offsetHeight`/`offsetWidth` are always `0`. A component that measures its own size (e.g. a virtualizer deciding which rows are visible) will render as empty, and the failure looks like a component bug rather than an environment limitation. Call `mockVirtualizedLayout` in a `beforeEach` — it mocks `offsetHeight`/`offsetWidth` to read the element's own inline style, falling back to a fixed size, so real measurements are distinguishable from unmeasured ones. -- **Runtime features newer than the browser floor**: to cover the path taken by a browser that lacks a global or method, delete the property reflectively and restore it in a `finally` — `Object.getOwnPropertyDescriptor` plus `Reflect.deleteProperty`. Don't write the typed reference (`Error.isError`), which stops compiling as soon as `lib` excludes the feature. See `withoutErrorIsError` in `src/utils/createErrorDetails.test.ts`. - **Jotai mount effects**: `store.get` reads an atom without mounting it, so `onMount` never runs. When the behavior under test depends on mounting, such as a subscription, cross-tab sync, or a listener, mount the atom first with `store.sub(atom, () => {})`. See the `subscribe` helper in `src/core/StateProvider/sessionScopedStorage.test.ts`. - **Errors**: assert the full error, not just that one was thrown. `expect(() => fn()).toThrow(new FooError(a, b))` — or `await expect(fn()).rejects.toThrow(new FooError(a, b))` for a rejected promise — deep-compares every property, so a wrong field fails the test. Prefer this over `toThrow(FooError)` (type only) or `toThrow("message")` (message only), which pass even when the code built the error with the wrong data. No need to catch the error and assert fields separately — the instance form already covers them. diff --git a/package.json b/package.json index 29230996e..63bbbd0bf 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "dependencies": {}, "devDependencies": { "@tanstack/eslint-plugin-query": "5.104.2", + "eslint-plugin-baseline-js": "0.7.4", + "eslint-plugin-es-x": "10.0.1", "eslint-plugin-react-hooks": "7.1.1", "fast-check": "^4.10.2", "typescript": "^7.0.2", diff --git a/packages/graph-explorer/.browserslistrc b/packages/graph-explorer/.browserslistrc index 18bc64a04..a654da080 100644 --- a/packages/graph-explorer/.browserslistrc +++ b/packages/graph-explorer/.browserslistrc @@ -1,7 +1,8 @@ -# Baseline Widely available on 2026-10-04, per baseline-browser-mapping. +# Baseline Widely available on 2026-10-04, per baseline-browser-mapping, with +# Chrome and Edge at 124 for AbortSignal.timeout (Widely available 2026-10-18). # vite.config.ts derives build.target from this list. -chrome >= 123 -edge >= 123 +chrome >= 124 +edge >= 124 firefox >= 124 safari >= 17.4 ios_saf >= 17.4 diff --git a/packages/graph-explorer/buildTarget.test.ts b/packages/graph-explorer/buildTarget.test.ts index 0b13b620a..3a4ea7126 100644 --- a/packages/graph-explorer/buildTarget.test.ts +++ b/packages/graph-explorer/buildTarget.test.ts @@ -35,8 +35,8 @@ describe("toBuildTarget", () => { describe("browserslistBuildTarget", () => { it("builds for the browsers in .browserslistrc", () => { expect(browserslistBuildTarget()).toStrictEqual([ - "chrome123", - "edge123", + "chrome124", + "edge124", "firefox124", "ios17.4", "safari17.4", diff --git a/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts b/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts index d5c8c24d2..35cbd6cc7 100644 --- a/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts +++ b/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts @@ -137,6 +137,7 @@ function createFetchTimeout( return null; } + // oxlint-disable-next-line baseline-js/use-baseline -- inside the floor at Chrome 124; the plugin's bundled data predates its 2026-10-18 Widely date return { timeoutMs, signal: AbortSignal.timeout(timeoutMs) }; } diff --git a/packages/graph-explorer/src/modules/SearchSidebar/ShowRawResponseDialog.tsx b/packages/graph-explorer/src/modules/SearchSidebar/ShowRawResponseDialog.tsx index 490f4b205..438ef9dc7 100644 --- a/packages/graph-explorer/src/modules/SearchSidebar/ShowRawResponseDialog.tsx +++ b/packages/graph-explorer/src/modules/SearchSidebar/ShowRawResponseDialog.tsx @@ -72,6 +72,7 @@ function CopyToClipboardButton({ const copyToClipboard = async () => { try { + // oxlint-disable-next-line baseline-js/use-baseline -- writeText is Widely available; the feature also covers Clipboard.write await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); diff --git a/packages/graph-explorer/src/utils/createErrorDetails.test.ts b/packages/graph-explorer/src/utils/createErrorDetails.test.ts index eec69ba2d..d7e738762 100644 --- a/packages/graph-explorer/src/utils/createErrorDetails.test.ts +++ b/packages/graph-explorer/src/utils/createErrorDetails.test.ts @@ -437,37 +437,6 @@ describe("createErrorDetails", () => { }); }); - describe("browsers without Error.isError", () => { - it("returns name and message from a standard Error", () => { - const error = new Error("something broke"); - expect( - withoutErrorIsError(() => createErrorDetails(error)), - ).toStrictEqual({ - name: "Error", - message: "something broke", - }); - }); - - it("includes the nested cause chain as data", () => { - const root = new Error("ECONNREFUSED"); - const error = new Error("request failed", { cause: root }); - const details = withoutErrorIsError(() => createErrorDetails(error)); - expect(details.data).toBe( - JSON.stringify({ name: root.name, message: root.message }, null, 2), - ); - }); - - it("returns name and message from a DOMException", () => { - const error = new DOMException("The operation was aborted", "AbortError"); - expect( - withoutErrorIsError(() => createErrorDetails(error)), - ).toStrictEqual({ - name: "AbortError", - message: "The operation was aborted", - }); - }); - }); - describe("non-Error values", () => { it("returns 'Unknown Error' with JSON for a plain object", () => { const error = { code: "ECONNREFUSED" }; @@ -522,25 +491,6 @@ describe("createErrorDetails", () => { }); }); -/** - * Runs `fn` with `Error.isError` absent, as on browsers that do not implement - * it. - * - * Reflection keeps the property name out of the type system, so this stays - * compilable under a `lib` that predates `Error.isError`. - */ -function withoutErrorIsError(fn: () => T): T { - const descriptor = Object.getOwnPropertyDescriptor(Error, "isError"); - Reflect.deleteProperty(Error, "isError"); - try { - return fn(); - } finally { - if (descriptor) { - Object.defineProperty(Error, "isError", descriptor); - } - } -} - /** Stands in for a query value failure type that `createErrorDetails` has no specific branch for. */ class DetailedQueryValueError extends QueryValueError { readonly details = { someContext: "value" }; diff --git a/packages/graph-explorer/src/utils/fileData.ts b/packages/graph-explorer/src/utils/fileData.ts index 27779b984..827b05dd8 100644 --- a/packages/graph-explorer/src/utils/fileData.ts +++ b/packages/graph-explorer/src/utils/fileData.ts @@ -37,6 +37,7 @@ export async function saveFile( return; } + // oxlint-disable-next-line baseline-js/use-baseline -- feature-detected above, falls back to saveAs const fileHandle = await window.showSaveFilePicker({ suggestedName: defaultFileName, types: [ diff --git a/packages/graph-explorer/vite.config.ts b/packages/graph-explorer/vite.config.ts index c63f54334..7964a47c4 100644 --- a/packages/graph-explorer/vite.config.ts +++ b/packages/graph-explorer/vite.config.ts @@ -43,7 +43,9 @@ export default defineConfig(({ mode }) => { target: browserslistBuildTarget(), }, plugins: lazyPlugins(() => [ - tailwindcss(), + // Tailwind's own Lightning CSS pass has fixed browser targets. Vite's CSS + // minify compiles for build.cssTarget, which follows .browserslistrc. + tailwindcss({ optimize: false }), react(), babel({ presets: [reactCompilerPreset()], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6fe6bd436..6ddf2b654 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -191,10 +191,16 @@ importers: devDependencies: '@tanstack/eslint-plugin-query': specifier: 5.104.2 - version: 5.104.2(eslint@10.2.1(jiti@2.7.0))(typescript@7.0.2) + version: 5.104.2(eslint@10.12.0(jiti@2.7.0))(typescript@7.0.2) + eslint-plugin-baseline-js: + specifier: 0.7.4 + version: 0.7.4(eslint@10.12.0(jiti@2.7.0)) + eslint-plugin-es-x: + specifier: 10.0.1 + version: 10.0.1(eslint@10.12.0(jiti@2.7.0)) eslint-plugin-react-hooks: specifier: 7.1.1 - version: 7.1.1(eslint@10.2.1(jiti@2.7.0)) + version: 7.1.1(eslint@10.12.0(jiti@2.7.0)) fast-check: specifier: ^4.10.2 version: 4.10.2 @@ -709,6 +715,12 @@ packages: resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true + '@cacheable/memory@2.2.0': + resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==} + + '@cacheable/utils@2.5.0': + resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==} + '@csstools/color-helpers@6.1.2': resolution: {integrity: sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==} engines: {node: '>=20.19.0'} @@ -915,8 +927,8 @@ packages: resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.5.5': - resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/core@1.2.1': @@ -1081,6 +1093,15 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@keyv/bigmap@1.3.1': + resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.6.0 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + '@monaco-editor/loader@1.7.0': resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} @@ -3100,6 +3121,9 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cacheable@2.5.0: + resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -3394,6 +3418,24 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-plugin-baseline-js@0.7.4: + resolution: {integrity: sha512-WUl7oMZoGGMjeVXRzid8kNyehc2+YrgFnAbIPWhk3B6Kr39JGjCEMdUlkbQzxQ/eyD19SdXY1YyabRKs5YbQGw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + eslint: '>=9.29.0 <11' + + eslint-plugin-es-x@10.0.1: + resolution: {integrity: sha512-HfShsMg5LKr9Cpz8VTBOLlrExvnHiXG0hf+nZT3MfsskWbttUSC/9jpO5Y//nyZ9Aik8sRvInf2Pe3kSUcQhYQ==} + engines: {node: ^22.23.0 || ^24.18.0 || >=26.4.0} + peerDependencies: + eslint: '>=10.6.0' + + eslint-plugin-es-x@9.7.0: + resolution: {integrity: sha512-BT0pUw0toxtrHulYycGiUTL++qC11opyPYMuGUR7AgmuQ/N1YN1xLupF8rGxlaGqEqkqeedJuacCxxAcYgGkvg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + eslint: '>=9.29.0' + eslint-plugin-react-hooks@7.1.1: resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} engines: {node: '>=18'} @@ -3404,6 +3446,12 @@ packages: resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint-type-tracer@0.5.2: + resolution: {integrity: sha512-YiJmgk6OD1suPB4UC3M/PB+oxahf145jQQ5gFXFk9F9Y7puWwnqGYC2BtTB+FoHqdOohImeTt0rNk0lhhECNaQ==} + engines: {node: ^18 || >=20} + peerDependencies: + eslint: '>=8' + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3412,8 +3460,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.2.1: - resolution: {integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==} + eslint@10.12.0: + resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -3496,9 +3544,8 @@ packages: fflate@0.8.3: resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} + file-entry-cache@11.1.5: + resolution: {integrity: sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==} file-saver@2.0.5: resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} @@ -3511,9 +3558,8 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} + flat-cache@6.1.23: + resolution: {integrity: sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==} flat@6.0.1: resolution: {integrity: sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==} @@ -3608,6 +3654,10 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + hashery@1.5.1: + resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} + engines: {node: '>=20'} + hasown@2.0.4: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} @@ -3621,6 +3671,12 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hookified@1.15.1: + resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} + + hookified@2.2.0: + resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} + html-encoding-sniffer@7.0.0: resolution: {integrity: sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==} engines: {node: ^22.13.0 || >=24.0.0} @@ -3715,9 +3771,6 @@ packages: engines: {node: '>=6'} hasBin: true - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -3729,8 +3782,8 @@ packages: engines: {node: '>=6'} hasBin: true - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@5.6.0: + resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} klayjs@0.4.1: resolution: {integrity: sha512-WUNxuO7O79TEkxCj6OIaK5TJBkaWaR/IKNTakgV9PwDn+mrr63MLHed34AcE2yTaDntgO6l0zGFIzhcoTeroTA==} @@ -4202,6 +4255,10 @@ packages: pure-rand@8.4.2: resolution: {integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==} + qified@0.10.1: + resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} + engines: {node: '>=20'} + qs@6.16.0: resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} engines: {node: '>=0.6'} @@ -5146,6 +5203,18 @@ snapshots: dependencies: css-tree: 3.2.1 + '@cacheable/memory@2.2.0': + dependencies: + '@cacheable/utils': 2.5.0 + '@keyv/bigmap': 1.3.1(keyv@5.6.0) + hookified: 1.15.1 + keyv: 5.6.0 + + '@cacheable/utils@2.5.0': + dependencies: + hashery: 1.5.1 + keyv: 5.6.0 + '@csstools/color-helpers@6.1.2': {} '@csstools/css-calc@3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': @@ -5248,9 +5317,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@10.2.1(jiti@2.7.0))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.12.0(jiti@2.7.0))': dependencies: - eslint: 10.2.1(jiti@2.7.0) + eslint: 10.12.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -5263,7 +5332,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.5.5': + '@eslint/config-helpers@0.7.0': dependencies: '@eslint/core': 1.2.1 @@ -5360,6 +5429,14 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.6.0 + '@keyv/bigmap@1.3.1(keyv@5.6.0)': + dependencies: + hashery: 1.5.1 + hookified: 1.15.1 + keyv: 5.6.0 + + '@keyv/serialize@1.1.1': {} + '@monaco-editor/loader@1.7.0': dependencies: state-local: 1.0.7 @@ -6469,10 +6546,10 @@ snapshots: tailwindcss: 4.3.3 vite: '@voidzero-dev/vite-plus-core@1.1.0(@types/node@24.19.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.42.0)(tsx@4.21.0)(typescript@7.0.2)(yaml@2.9.0)' - '@tanstack/eslint-plugin-query@5.104.2(eslint@10.2.1(jiti@2.7.0))(typescript@7.0.2)': + '@tanstack/eslint-plugin-query@5.104.2(eslint@10.12.0(jiti@2.7.0))(typescript@7.0.2)': dependencies: - '@typescript-eslint/utils': 8.71.0(eslint@10.2.1(jiti@2.7.0))(typescript@7.0.2) - eslint: 10.2.1(jiti@2.7.0) + '@typescript-eslint/utils': 8.71.0(eslint@10.12.0(jiti@2.7.0))(typescript@7.0.2) + eslint: 10.12.0(jiti@2.7.0) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -6687,13 +6764,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.71.0(eslint@10.2.1(jiti@2.7.0))(typescript@7.0.2)': + '@typescript-eslint/utils@8.71.0(eslint@10.12.0(jiti@2.7.0))(typescript@7.0.2)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.2.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.71.0 '@typescript-eslint/types': 8.71.0 '@typescript-eslint/typescript-estree': 8.71.0(typescript@7.0.2) - eslint: 10.2.1(jiti@2.7.0) + eslint: 10.12.0(jiti@2.7.0) typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -7113,6 +7190,14 @@ snapshots: bytes@3.1.2: {} + cacheable@2.5.0: + dependencies: + '@cacheable/memory': 2.2.0 + '@cacheable/utils': 2.5.0 + hookified: 1.15.1 + keyv: 5.6.0 + qified: 0.10.1 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -7383,11 +7468,30 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@7.1.1(eslint@10.2.1(jiti@2.7.0)): + eslint-plugin-baseline-js@0.7.4(eslint@10.12.0(jiti@2.7.0)): + dependencies: + eslint: 10.12.0(jiti@2.7.0) + eslint-plugin-es-x: 9.7.0(eslint@10.12.0(jiti@2.7.0)) + + eslint-plugin-es-x@10.0.1(eslint@10.12.0(jiti@2.7.0)): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + eslint: 10.12.0(jiti@2.7.0) + eslint-type-tracer: 0.5.2(eslint@10.12.0(jiti@2.7.0)) + + eslint-plugin-es-x@9.7.0(eslint@10.12.0(jiti@2.7.0)): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + eslint: 10.12.0(jiti@2.7.0) + eslint-type-tracer: 0.5.2(eslint@10.12.0(jiti@2.7.0)) + + eslint-plugin-react-hooks@7.1.1(eslint@10.12.0(jiti@2.7.0)): dependencies: '@babel/core': 7.29.7 '@babel/parser': 7.29.8 - eslint: 10.2.1(jiti@2.7.0) + eslint: 10.12.0(jiti@2.7.0) hermes-parser: 0.25.1 zod: 4.6.7 zod-validation-error: 4.0.2(zod@4.6.7) @@ -7401,16 +7505,21 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 + eslint-type-tracer@0.5.2(eslint@10.12.0(jiti@2.7.0)): + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) + eslint: 10.12.0(jiti@2.7.0) + eslint-visitor-keys@3.4.3: {} eslint-visitor-keys@5.0.1: {} - eslint@10.2.1(jiti@2.7.0): + eslint@10.12.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.2.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.12.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 - '@eslint/config-helpers': 0.5.5 + '@eslint/config-helpers': 0.7.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.3 '@humanfs/node': 0.16.8 @@ -7427,7 +7536,7 @@ snapshots: esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 + file-entry-cache: 11.1.5 find-up: 5.0.0 glob-parent: 6.0.2 ignore: 5.3.2 @@ -7528,9 +7637,9 @@ snapshots: fflate@0.8.3: {} - file-entry-cache@8.0.0: + file-entry-cache@11.1.5: dependencies: - flat-cache: 4.0.1 + flat-cache: 6.1.23 file-saver@2.0.5: {} @@ -7550,10 +7659,11 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flat-cache@4.0.1: + flat-cache@6.1.23: dependencies: + cacheable: 2.5.0 flatted: 3.4.4 - keyv: 4.5.4 + hookified: 1.15.1 flat@6.0.1: {} @@ -7655,6 +7765,10 @@ snapshots: dependencies: has-symbols: 1.1.0 + hashery@1.5.1: + dependencies: + hookified: 1.15.1 + hasown@2.0.4: dependencies: function-bind: 1.1.2 @@ -7667,6 +7781,10 @@ snapshots: dependencies: hermes-estree: 0.25.1 + hookified@1.15.1: {} + + hookified@2.2.0: {} + html-encoding-sniffer@7.0.0(@noble/hashes@1.8.0): dependencies: '@exodus/bytes': 1.16.0(@noble/hashes@1.8.0) @@ -7751,17 +7869,15 @@ snapshots: jsesc@3.1.0: {} - json-buffer@3.0.1: {} - json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} - keyv@4.5.4: + keyv@5.6.0: dependencies: - json-buffer: 3.0.1 + '@keyv/serialize': 1.1.1 klayjs@0.4.1: {} @@ -8173,6 +8289,10 @@ snapshots: pure-rand@8.4.2: {} + qified@0.10.1: + dependencies: + hookified: 2.2.0 + qs@6.16.0: dependencies: es-define-property: 1.0.1 diff --git a/vite.config.ts b/vite.config.ts index 0eb39aad9..1e29cb141 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -206,6 +206,36 @@ export default defineConfig({ node: true, }, }, + { + files: [ + "packages/graph-explorer/src/**/*.{ts,tsx}", + "packages/shared/src/**/*.ts", + ], + excludeFiles: [ + "**/*.test.{ts,tsx}", + "**/testing/**", + "**/testUtils/**", + ], + jsPlugins: ["eslint-plugin-baseline-js", "eslint-plugin-es-x"], + rules: { + "baseline-js/use-baseline": [ + "error", + { + available: "widely", + includeWebApis: { preset: "auto" }, + includeJsBuiltins: { preset: "auto" }, + ignoreFeatures: ["html-wrapper-methods", "top-level-await"], + }, + ], + "es-x/no-using-declarations": "error", + "es-x/no-regexp-duplicate-named-capturing-groups": "error", + "es-x/no-regexp-modifiers": "error", + "es-x/no-import-attributes": "error", + "es-x/no-json-modules": "error", + "es-x/no-dynamic-import-options": "error", + "es-x/no-trailing-dynamic-import-commas": "error", + }, + }, { files: ["packages/graph-explorer/src/**/*.{ts,tsx}"], excludeFiles: ["packages/graph-explorer/src/connections/**"],