Imported from kronor-io/graphql-engine (
frontend/libs/metadata/api/AGENTS.md). Install upstream withnpx skills add kronor-io/graphql-engine --skill api. Copyright stays with the author.
AGENTS.md (metadata/api)
Nx project metadata-api, import path @hasura/metadata/api. src/index.ts
re-exports ./hooks, ./utils, ./types, ./api, and
./mocks/metadata.mock (flat barrel, no default export).
Purpose
Provides the React Query (@tanstack/react-query) hooks and low-level
fetch-wrapper functions the Hasura console frontend uses to talk to a
GraphQL Engine server's Metadata API, Schema/GraphQL endpoint, and
run_sql/migrate endpoints. It centers on useMetadata (fetches/caches the
whole exported metadata doc) and useMetadataMigration (generic mutation
wrapper for any metadata query type, with built-in cache invalidation,
out-of-date/conflict handling, and toast notifications), on top of which
dozens of narrower hooks are built for tracking/untracking tables and
functions, managing permissions, relationships, remote schemas, REST
endpoints, query collections, sources, and running SQL. It also exposes plain
(non-hook) API functions (exportMetadata, runSQL, runGraphQL,
runIntrospectionQuery, etc.) for use outside React, plus small utilities for
error toasting and inconsistent-metadata parsing.
Modules
- api/ — Framework-agnostic network calls, each taking
{ url, fetchJson, ... }(NetworkArgsfromtypes.ts, wrapping@hasura/shared/utils'sFetchJson).catalog.ts—fetchCatalogState/setCatalogState: get/set console UI state (notifications-read state, onboarding flag) viaget_catalog_state/set_catalog_statemetadata queries.exportMetadata.ts—exportMetadata: POSTs{type: 'export_metadata', version: 2}, returns fullMetadata(@hasura/shared/types).getMigrationStatus.ts—getMigrationStatus(url): GETs CLI-server migrate-settings endpoint, mapsmigration_mode === 'true'to'healthy'/'unhealthy'.notification.ts—fetchConsoleNotifications: runs a raw GraphQL query againstconsole_notificationswith scope/time filtering; throws firstGraphQLErroriferrorspresent.runGraphQL.ts—runGraphQL<T>: POSTs a GraphQL body; throws the firstresult.data.errors[0]asGraphQLError(response status is 200 even on GraphQL errors).runIntrospectionQuery.ts—runIntrospectionQuery: callsrunGraphQLwithgetIntrospectionQuery({descriptions:true, schemaDescription:true, typeDepth:7}).runMetadataQuery.ts—runMetadataQuery<ResponseType>+ typesTMigrationSingleQuery/TMigrationBulkQuery/TMigrationQuery: generic POST for any metadata query type. This is the foundational type used by nearly every mutation hook in the package.runSQL.ts—getRunSqlQuery/runSQL/runSQLBulk;getRunSqlTypemaps driver →{prefix}_run_sqlwherepostgres/alloyboth map to prefixpg.runSQLMigrate.ts—runSQLMigrate(CLI-server/apis/migrateendpoint) andgetDownQueryComments(auto-generates a commented-out "down" migration SQL block when none is supplied).version.ts—loadServerVersion,loadLatestServerVersion(checks againstupdateCheckendpoint, tagged withagent=console).index.ts— barrel re-exporting all of the above.
- types.ts —
NetworkArgs<T>({url, fetchJson}),SchemaResponse,TableRelationship. - hooks/constants.ts —
DEFAULT_STALE_TIME(5 min), query keys (METADATA_QUERY_KEY,INCONSISTENT_METADATA_QUERY_KEY,MIGRATION_STATUS_QUERY_KEY),MAX_ALLOWED_MIGRATION_LENGTH(255 − 14 for unix-epoch prefix),defaultQueryOptions. - hooks/metadata/ — Core metadata read/write hooks.
useMetadata.ts—useMetadata(selector?, options): React Query wrapper aroundexportMetadata; keyed onMETADATA_QUERY_KEY;refetchOnWindowFocus: true; defaultstaleTime5 min.useMetadataHelpers.ts—useMetadataHelpers()returns{fetchMetadata, fetchSource}, imperative fetch/refetch helpers used inside mutation callbacks; throws if source not found.useMetadataMigration/— the central mutation primitive.useMetadataMigration.ts—useMetadataMigration<ResponseType, ArgsType>(options): wrapsrunMetadataQueryinuseMutation; supportserrorTransform; on success callsusePostMetadataMigration()(cache invalidation + CLI sync) then the user'sonSuccess; on error callstoastMetadataOutOfDateError(detects HTTPconflictcode and prompts user to refetch) then the user'sonError. Nearly every other hook in the package composes this.usePostMetadataMigration.ts— inconsoleMode === 'cli', triggers an extra query to re-export metadata from the CLI server's local filesystem; always invalidates metadata cache.
useMetadataVersion.ts—useMetadataVersion()=useMetadata(d => d.resource_version).useInconsistentMetadata.ts—useInconsistentMetadata,useFetchInconsistentMetadata(imperative),useInvalidateInconsistentMetadata,inconsistentSourcesSelector.useInvalidateMetadata.ts—useInvalidateMetadata()/standaloneinvalidateMetadata(queryClient): invalidates both metadata and inconsistent-metadata query keys together (always paired).useReloadMetadata.tsx—useReloadMetadata(): computes which sources/remote schemas are inconsistent and issues areload_metadataquery scoped to just those, unless caller forcesshouldReloadAllSources/shouldReloadRemoteSchemas.useReplaceMetadata.ts—useReplaceMetadata(): wrapsreplace_metadata; parses responsewarnings[].codeinto specific toast titles (illegal-event-trigger-name,source-cleanup-failed, else generic).useReplaceMetadataFromFile.ts— JSON-parses a file string then callsuseReplaceMetadata; toasts a parse error and callsonErrorwithout ever callingreplaceMetadataif parsing fails.useResetMetadata.ts—useResetMetadata():clear_metadata; always invalidates in a.finally()even on error.useDropInconsistentMetadata.ts— thin wrapper issuingdrop_inconsistent_metadata.
- hooks/graphql/ —
useGraphQLMutation(generic named GraphQL mutation runner),useIntrospectSchema(builds aGraphQLSchemaviabuildClientSchema, cached underINTROSPECT_SCHEMA_QUERY_KEY). - hooks/query/ —
useRunSQLQuery/useRunSQLCommand(select vs. command variants ofrunSQL),useRunSQLBulk. - hooks/table/ —
useTrackTable,useTrackTables(bulk; stripslogical_modelout ofconfiguration),useUntrackTable,useUntrackTables. All derive the metadata query-type prefix from the source driver viagetDriverPrefix(@hasura/metadata/helpers) and require auseMetadataHelpers().fetchSourceround-trip first. - hooks/function/ —
useTrackFunction/useTrackFunctions,useUntrackFunctions,useSetFunctionCustomization,useRetrackFunction(drop+re-track as onebulkmigration, merging old and newconfiguration; matches old function viaareFunctionsEqual). - hooks/permission/ —
useCreateTablePermission(exports only the query buildergetCreatePermissionQuery, no hook),useDropTablePermission/useDropTablePermissionMultipleRoles,useUpdateTablePermission(stripslimitfrom select-permission definition whenlimitEnabledis false),useCreateFunctionPermission/useDropFunctionPermission,useAddInheritedRole/useUpdateInheritedRole(drop+add as bulk)/useDropInheritedRole. - hooks/relationship/ —
useCreateRemoteRelationship,useUpdateRemoteRelationship,useDeleteRemoteRelationship,useRenameRelationship,useDropRelationship,useDropRelationships(bulk_atomic; auto-detects whether each relationship is "remote" to pick the right drop query type), anduseSuggestedRelationships/:useSuggestedRelationships— queries{driver}_suggest_relationships, cross-references already-tracked FK relationships from metadata (viaselectors/selectors.ts'sgetTrackedSuggestedRelationships) and computes GraphQL-safe constraint names (utils.ts'saddConstraintName).mock.ts— Chinook-dataset fixtures for tests/stories only.
- hooks/remoteSchema/ —
useAddRemoteSchema,useUpdateRemoteSchema,useRemoveRemoteSchema,useIntrospectRemoteSchema,useMetadataRemoteSchemas.ts(exportsuseListRemoteSchemas),useReloadRemoteSchema,useDropRemoteSchemaPermissions/useDropRemoteSchemaPermissionMultipleRoles(confirms viagetConfirmationbefore dropping),useSaveRemoteSchemaPermission(drop+recreate if a permission for that role already exists). - hooks/rest/ — REST endpoint CRUD built on top of query collections:
useAddRestEndpoint(createsallowed-queriescollection first if missing),useCreateRestEndpoints,useDeleteRestEndpoints,useDropRestEndpoint,useEditRestEndpoint,useRestEndpoint(pure metadata lookup, no network), anduseRestEndpointDefinitions/— generates REST endpoint + GraphQL query definitions from theGraphQLSchemareturned byuseIntrospectSchema(graphql-js); derives per-operation HTTP path/method perEndpointType(READ/READ_ALL/CREATE/UPDATE/DELETE). - hooks/source/ —
useDropSource({driver}_drop_source, alwayscascade: true). - hooks/queryCollections/ —
useCreateQueryCollection(also exportscreateAllowedQueriesIfNeeded, reused by REST-endpoint hooks),useAddOperationsToQueryCollection,useEditOperationInQueryCollection,useMoveOperationsToQueryCollection(also re-points any REST endpoint referencing a moved query),useRemoveOperationsFromQueryCollection(also drops any REST endpoint built on a removed query),useDeleteQueryCollections,useRenameQueryCollection. - hooks/migration/useMigrationStatus.ts —
useMigrationStatus(): only meaningful inconsoleMode === 'cli'(returns'off'immediately in server mode withstaleTime: Infinity);updateMigrationModeStatus()PUTs the toggled state to the CLI server. - hooks/server/ —
useServerConfig(staleTime: Infinity, since server config doesn't change at runtime),useServerVersion(imperativeinitServerVersion(); computesfeaturesCompatibilityviagetFeaturesCompatibilityfrom@hasura/shared/utils, fetches latest-available version for update-nag banners). - utils/ —
error.tsx:toastMetadataOutOfDateError(detectsHttpErrorwithdata.code === 'conflict', shows a toast with a "Fetch metadata" action button) andhandleMigrationStatusError(CLI-modealert()messaging).hyperlinkErrorMessageLink.tsx:createTextWithLinks(regex-based auto-linkifier for warning/error message text).inconsistentMetadata.ts:getRemoteSchemaNameFromInconsistentObjects(documented "HACK" — see Gotchas) andgetSourceFromInconsistentObjects. - mocks/metadata.mock.ts — Test/story fixtures:
metadataTables,metadataSource(sqlite),metadataWithoutSources,metadataWithSourcesAndTables,metadataWithResourceVersion(deliberately malformed for edge-case testing). Exported from the public barrel, so consumers outside this lib can import these mocks too. - stories/ — Storybook stories plus MSW mock handlers/data — not part
of the public API surface (not re-exported from
src/index.ts).
Gotchas
useMetadataMigrationis the load-bearing abstraction almost everything else composes; itsonSuccess/onErroralways run cache-invalidation and out-of-date-conflict toasting before/alongside the caller's own callbacks — don't bypass it by callingrunMetadataQuerydirectly unless you also handle invalidation yourself.useInvalidateMetadata/invalidateMetadataalways invalidate bothMETADATA_QUERY_KEYandINCONSISTENT_METADATA_QUERY_KEYtogether — there's no way to invalidate just one via this helper.- Many hooks (
useTrackTable,useTrackFunction, permission/relationship hooks, etc.) must resolve the data source's driverkindfirst viauseMetadataHelpers().fetchSource/fetchMetadatabefore building the query type string viagetDriverPrefix— order-sensitive: fetch source → derive prefix → build query → mutate. getRunSqlTypemaps bothpostgresandalloydrivers to thepgprefix — a non-obvious special case; other native drivers use their own name as prefix.hooks/remoteSchema/useReloadRemoteSchema.tsreferences a bare, undefinednameidentifier in its success/error toast messages instead of theremoteSchemaNameparameter — likely throws/resolves toundefinedat runtime when those branches execute; check before relying on that message text.utils/inconsistentMetadata.ts'sgetRemoteSchemaNameFromInconsistentObjectsis explicitly documented as a fragile "HACK": it extracts the remote schema name by taking the last whitespace-separated token of a free-textnamefield — breaks if that message format changes server-side.useReplaceMetadata's warning-title mapping is order-sensitive and silently falls back to a generic title for any unrecognized code — easy to misattribute an unrelated warning code.- Several hooks assume browser globals:
getConfirmation/alert()-based flows (useDropRemoteSchemaPermissions,handleMigrationStatusError) are not SSR-safe. - Heavy inter-dependency on other
@hasura/*packages:@hasura/shared/types,@hasura/metadata/helpers(getDriverPrefix,MetadataSelectors,areTablesEqual,areFunctionsEqual,getTableDisplayName),@hasura/shared/hooks(useAuthFetchJson),@hasura/shared/context(useAppContext,Endpoints),@hasura/shared/ui(hasuraToast,showErrorNotification),@hasura/shared/types(HttpError,EnvVars,ADMIN_SECRET_HEADER_KEY),@hasura/shared/utils(requestJson,getErrorMessage,getConfirmation,formatGraphQL,getFeaturesCompatibility,isNotNull). This package cannot be used standalone without anAppContextprovider supplyingendpoints/envVars. - Do not import from
@hasura/metadata/data-sourcehere: it already depends on this package, and the resulting project-graph cycle makes Nx drop the^buildedges fromconsole-ce:build/console-ee:buildto the buildable libs, so webpack races theirdist/rebuild. Shared pieces belong in@hasura/shared/types(e.g.NotImplementedError,SchemaTable). useRestEndpointDefinitionsdepends on GraphQL introspection succeeding and uses heuristics to locate query/mutation root types when custom root-field namespaces are configured; assumespk_columns/pkColumnsargument naming for update operations, throwing if neither is found.
Testing
Vitest (vite.config.mts, environment: 'jsdom', shared setup file
../../../tools/test-setup/setupTests.ts). 4 test files:
hooks/metadata/useMetadataMigration/useMetadataMigrationServerMode.test.tsx,
hooks/metadata/useMetadataMigration/useMetataMigrationCLIMode.test.tsx
(note the typo "Metata" in the filename),
hooks/relationship/useSuggestedRelationships/selectors/selectors.test.ts,
hooks/rest/useRestEndpointDefinitions/utils.test.ts. Run via nx test metadata-api.
Mocking metadata endpoints (@hasura/shared/testing)
Hook tests that hit /v1/metadata should use the shared MSW handlers instead
of hand-rolling a setupServer body:
import { setupServer } from 'msw/node';
import { handlers } from '@hasura/shared/testing';
// Each handlers() call owns an isolated in-memory metadata document, so
// mutations never leak across tests.
const server = setupServer(...handlers());
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers(...handlers())); // fresh state per test
afterAll(() => server.close());
- The handlers match
POST */v1/metadata,GET */v1alpha1/config, andGET */apis/metadataby path (wildcard origin), so they work for both server mode (http://localhost/v1/metadata) and CLI mode (apiPortorigin for the CLI export).resetHandlers(...handlers())resets to a fresh document each test — plainresetHandlers()would keep the mutated state and cause cross-test flakiness. export_metadatareturns the current document (withresource_version); a successful write returns{ message: 'success' }and bumpsresource_version(souseMetadataVersionre-exports the new value after invalidation); a reducer error returns itsstatus+ error JSON. Action types without a domain handler (e.g.pg_create_remote_relationship) succeed and bump the version.- Use
server.use(...handlers({ initialData, config, delay }))or add extrahttp.*handlers to override per test. - Render hooks with
testWrapper/testRenderWithClientfrom@hasura/shared/testing(not@hasura/shared/ui); they provide theQueryClientProvider,AppContext, and aMemoryRouter(needed byuseAuthFetchJson).Buttonand other components come from@hasura/shared/ui.
