Skip to content

refactor: comprehensive cleanup and critical bug fixes#71

Open
productdevbook wants to merge 23 commits intomainfrom
refactor/comprehensive-cleanup
Open

refactor: comprehensive cleanup and critical bug fixes#71
productdevbook wants to merge 23 commits intomainfrom
refactor/comprehensive-cleanup

Conversation

@productdevbook
Copy link
Owner

Summary

  • Fix 7 critical bugs: defineDirective __schema loss, defu merge order, TypedDocumentString missing, hardcoded yoga endpoint, apollo handler per-request recreation, health check self-fetch, rollup chunking dead code
  • Refactor setup to resolver chain pattern (Nitro v3 style): 14-step monolith → sequential resolver functions
  • Consolidate scanner/codegen: eliminate duplicates, unify ignore patterns, share ScanContext, parallelize external services
  • Unify type system: remove duplicate types across core/nitro, extract StandardSchemaV1, fix null→undefined
  • Extract shared WS handler: byte-for-byte identical yoga-ws/apollo-ws → single _ws-handler.ts
  • Add 183 new tests (834 → 1017): setup resolvers, rollup integration, type generation, WS handler, codegen config, string utils, ignore patterns

Test plan

  • pnpm vitest run — 44 files, 1017 tests pass
  • pnpm build — 206 files, clean
  • pnpm playground:nitro build — schemas: 4, resolvers: 5, server built successfully
  • Manual test: pnpm playground:nuxt
  • Manual test: pnpm playground:federation

🤖 Generated with Claude Code

productdevbook and others added 23 commits March 17, 2026 07:35
- fix(define): preserve __schema property on returned directive object
  instead of losing it during spread (was always undefined before)
- fix(codegen): correct defu merge order so user config takes priority
  over defaults in client type generation
- fix(codegen): enable typed-document-node plugin when documentMode is
  'string' to generate TypedDocumentString class definition
- fix(codegen): remove duplicate UseSubscriptionSessionReturn export
- fix(routes/yoga): read endpoint from moduleConfig instead of hardcoded
  '/api/graphql', preserve response headers in early-return path
- fix(routes/apollo): cache h3Handler to avoid recreation per request
- fix(routes/health): use direct GraphQL execution instead of self-fetch
  which fails in serverless environments
- fix(routes/debug): add runtime production guard to prevent accidental
  exposure
- fix(rollup): remove dead return that disabled chunking, add
  advancedChunks and codeSplitting guards
- fix(virtual.d.ts): remove orphan module declarations for
  server-scalars and client-schema that had no backing generators

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
graphql-yoga-ws.ts and apollo-server-ws.ts were byte-for-byte identical.
Extract shared logic into _ws-handler.ts and reduce both files to thin
wrappers that import wsHooks from the shared module.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Remove duplicate CoreSecurityConfig from core/server/types.ts
- Remove duplicate directive types from nitro/types.ts, import from
  core/types/define.ts instead
- Move StandardSchemaV1 to dedicated file (core/types/standard-schema.ts)
- Replace GraphQLArgumentType 35-literal union with template literal type
- Fix DEFAULT_PATHS_CONFIG null values to undefined for type safety

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Refactor 14-step monolithic setupNitroGraphQL into a sequential
resolver chain (inspired by Nitro v3's config resolver pattern).

- Each setup step is now an independent resolver function
- Extract resolveSecurityConfig to dedicated security.ts module
- Extract regenerateTypes to type-generation.ts (eliminates 3x
  copy-paste across setup, file-watcher, and close hooks)
- Remove dead setupNuxtIntegration code
- Remove dead switch branches in resolveBuildDirectories
- Fix circular import between setup.ts and file-watcher.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Scanner:
- Use shared getImportId() instead of duplicated hash alias logic
- Remove scanGraphqlCore (identical to scanSchemasCore)
- Unify ignore patterns into DEFAULT_IGNORE_PATTERNS constant
- Create ScanContext once and share across all 4 scanners
- Fix parseSingleFile to log errors via consola.debug instead of
  silently swallowing them

Codegen:
- Pass schema string directly to avoid disk read round-trip
- Parallelize external service type generation with Promise.all
- Extract per-service logic into generateExternalServiceTypes

Utilities:
- Merge toPascalCase and capitalize into shared string.ts util
- Export from core/utils barrel

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Regenerated SDK files now include TypedDocumentString class definition
via typed-document-node plugin (documentMode: 'string' fix).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…re overhaul

- TypedDocumentString: replace raw string prepend with proper @graphql-codegen
  plugin (src/core/codegen/plugins/typed-document-string.ts)
- Type system: make core types authoritative by extending actual codegen plugin
  types (TypeScriptPluginConfig, TypeScriptResolversPluginConfig, etc.)
- Nitro types re-export from core with backward-compatible aliases
  (CodegenServerConfig, CodegenClientConfig, GenericSdkConfig)
- Split src/nitro/codegen.ts (311 LOC) into codegen/{server,client,external}-types.ts
- Split src/nitro/virtual/generators.ts (291 LOC) into 10 focused modules
- Split src/nitro/types.ts (536 LOC) into types/{config,augmentation,define}.ts
- Eliminate 14 of 17 `as any` casts across src/ (remaining 3 in nuxt.ts @ts-nocheck)
- Strongly type CoreCodegenConfig and CoreExternalService in core/types/config.ts
- Simplify ScanDocumentsOptions to minimal interface (only uses documents field)
- Remove redundant CLI adapter externalServices mapping
- Rename mergedSdkConfig → resolvedSdkConfig with JSDoc
- Fix vitest coverage exclusion for new types directory structure
- Clean stale TODO and comments

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…e dead abstractions

Architecture improvements identified through systematic review:

- Split client.ts (665→242 LOC): extract subscription-extractor.ts (pure GraphQL)
  and vue-subscription-builder.ts (Vue composables) from framework-agnostic core
- Unify SecurityConfig: delete duplicate in nitro/types, re-export from core
  (CoreSecurityConfig is now the single source of truth)
- Remove dead ScanAdapter interface and implementation: scanning goes through
  core functions directly, the adapter layer was designed but never wired up
- Type virtual module stubs: replace `any` with proper IResolvers, DebugInfoStub,
  and directive types for build-time type safety
- Remove `(i: any)` casts from debug route (types now flow from stubs)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Create src/core/server/apollo.ts: Apollo Server factory following
  the same pattern as yoga.ts (shared CoreServerOptions, security defaults)
- Slim Nitro Apollo route from 104→36 LOC: thin H3 wrapper calling core factory
- Extract APOLLO_USER_FACING_ERROR_CODES constant, type formatError properly
  (GraphQLFormattedError instead of any)
- Separate CLIGraphQLOptions from NitroGraphQLOptions: CLI-specific fields
  (rootDir, buildDir, ignore, watch, runtime) no longer pollute the
  module config API that users see in nitro.config.ts
- Simplify CLIContext type to just CLIGraphQLOptions (was a complex intersection)
- Type virtual module stubs properly (IResolvers, DebugInfoStub, null pubsub)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Replace DirectiveParser class + singleton with pure functions:
  parseDirectivesFromFile() replaces parser.parseDirectives()
- Add OxcNode/OxcProperty interfaces for oxc-parser AST nodes,
  eliminating all `any` types from AST traversal (was 15+ occurrences)
- Remove lazy-load pattern: use direct `parseSync` import from oxc-parser
  (already used this way in ast-scanner.ts)
- Function is now synchronous (parseSync), removing unnecessary async overhead
- Type ParsedDirective.defaultValue as union instead of `any`

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Type ast-scanner.ts parseExports: add OxcASTNode interface replacing
  `{ body: any[] }` with typed AST node shape
- Type directive defaultValue: replace `any` with
  `string | number | boolean | null` union (DirectiveDefaultValue)
- Type virtual.d.ts federation field: `any` → `Record<string, unknown>`
- Type apollo.ts context function: `any` → `TContext` with safe cast
- Type codegen pluginMap: `{ plugin: any }` → `{ plugin: unknown }`
- Remove dead WatchConfig.enabled field (was never read by file watcher)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…tion

- Fix Rolldown crash: check `'codeSplitting' in output` instead of
  truthiness — Rolldown throws when manualChunks is set alongside
  codeSplitting even when false
- Fix extend package crash: warn and skip instead of throwing when
  package config is not found (e.g., workspace packages without
  nitro-graphql.config.ts during dev rescan)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Virtual module code generation reads nitro.scan* arrays during Rolldown
dev build. The dev:start hook's rescan was resetting these arrays
(scanLocalFiles), causing a window where extend schemas/resolvers were
missing from virtual modules.

Fix: skip rescan entirely when extend sources are configured, since
extend packages don't change during dev. Also make scanLocalFiles
collect all results before assigning to prevent partial state.

Additionally: add debug logging to loadPackageConfig catch block
(was silently swallowing all errors).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Config system cleanup based on full dependency analysis:

- Remove CoreContext (dead — never called by any production code)
- Remove CoreGraphQLOptions (over-engineered, 11 fields defined but only 4 populated)
- Flatten CoreConfig: security/federation/codegen/externalServices as direct fields
  instead of nested in a never-fully-populated CoreGraphQLOptions bag
- Remove FrameworkAdapter.createCoreContext (dead interface method)
- Make ExternalGraphQLService extend ExternalServiceCodegenConfig (was fully
  redeclaring all fields without type relationship)
- Make FederationConfig extend CoreFederationConfig (was parallel hierarchy)
- Deduplicate LocalDirExtendSource: defined in core, re-exported from nitro
- Remove mergeGraphQLOptions (unused utility)
- Fix CLI buildDir default: `.nitro-graphql` → `.graphql` (matches DEFAULT_CLI_CONFIG)
- Simplify both adapters to pass config fields directly

Before: 3 parallel type hierarchies (ExternalService, Federation, Paths)
After: single inheritance chain from core → nitro

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…tion

Two algorithmic improvements:

1. Virtual module eager snapshots:
   - registerAllVirtualModules() now generates code eagerly and captures
     it as a string, instead of storing a closure that reads nitro.scan*
     lazily at Rolldown build time
   - Eliminates the root cause of race conditions between concurrent
     rescan (dev:start hook) and Rolldown virtual module resolution
   - New refreshVirtualModules() explicitly re-snapshots after file
     watcher rescans, ensuring hot reload works correctly

2. Path resolution simplification:
   - Replace 7 separate compiled regexes with single `/{(\w+)}/g` pattern
   - Single pass replacement via lookup table instead of 7 chained .replace()
   - Extensible: adding a new placeholder requires only a lookup entry,
     not a new regex constant

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Replace `import from '../index'` (barrel) with direct module imports
  to prevent circular dependency risk and ensure correct mock interception
- Extract parseResolverFiles() and parseDirectiveFiles() helpers to DRY
  the repeated parse-loop pattern across scanPackageSource, scanLocalDirSource,
  and scanExplicitPaths
- Use DEFAULT_IGNORE_PATTERNS constant instead of hardcoded array in
  scanLocalDirSource
- Add emptyResult() factory to eliminate 6 repeated object literals
- Parallelize schema/resolver/directive glob scans in scanLocalDirSource
  (was sequential, now Promise.all)
- Update test mocks to match new direct import paths

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Extract repeated patterns in schema-loader.ts:

- resolveServiceSources(): resolve headers + schemas from service config (was inline 3x)
- buildLoaders(): auto-detect URL/file loaders from schema sources (was inline 4x)
- loadWithLoaders(): single entry point for loadSchemaSync with auto loaders (was 4 variants)
- resolveSchemaFilePath(): compute cache path (was inline 2x)
- needsUpdate(): extract cache staleness check from downloadAndSaveSchema
  (was deeply nested in a multi-branch conditional)

227 → 181 LOC, zero behavior change, same test coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…an arrays

BREAKING STRUCTURAL CHANGE: The scan pipeline now produces frozen state
snapshots instead of mutating 6 separate arrays on the Nitro instance.

New architecture:
  scanLocalFiles() → createScanState() → frozen GraphQLScanState
  resolveExtendConfig() → mergeScanState() → new frozen snapshot
  performGraphQLScan() → nitro.graphql.state = finalState (atomic write)

All consumers (virtual modules, codegen, logging) now read from the
single `nitro.graphql.state` snapshot. State is never mutated — updates
produce new frozen objects via Object.freeze().

Benefits:
- Race conditions impossible: state is immutable once assigned
- Atomic updates: no partial state visible to concurrent readers
- Single source of truth: nitro.graphql.state replaces 6 mutable arrays
- Extend merge is pure: mergeScanState(base, extend) → new state

Backward compatibility maintained:
- Legacy fields (nitro.scanSchemas, nitro.graphql.extendConfigs, etc.)
  are synced from state after each scan for existing tests/consumers
- All fields marked @deprecated with migration guidance
- All 1017 tests pass without changes to test assertions

New files:
- src/nitro/state.ts — emptyScanState, createScanState, mergeScanState
- src/nitro/types/augmentation.ts — GraphQLScanState interface

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…works

- Extract resolveSecurityDefaults() to core/server/types.ts — single source
  of truth for security config defaults, used by both Yoga and Apollo factories
  (was duplicated as inline object literal in each)
- Move BASE_SCHEMA_DEF (SchemaDefinition wrapper) to core/schema/builder.ts
  alongside the BASE_SCHEMA string constant it wraps
- Add BASE_SCHEMA_DEF to Apollo route (was missing — extend type Query
  syntax requires base Query/Mutation types to exist)
- Both route handlers now follow identical pattern:
  schemas: [BASE_SCHEMA_DEF, ...schemas]

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…ding

Based on fresh-eyes readability audit:

Naming fixes:
- Rename resolvedSdkConfig → sdkFileConfig in nitro/codegen/ to avoid
  collision with core/codegen/client.ts's resolvedSdkConfig (different types)
- Mark CodegenServerConfig, CodegenClientConfig, GenericSdkConfig as
  @deprecated in public exports (prefer ServerCodegenConfig etc.)
- Mark FILE_CONTEXT_TS constant as @deprecated with JSDoc

Documentation:
- Add module-level comment to paths.ts explaining its dual responsibility
  (path placeholders + generation control)
- Document nitro.graphql.dir vs *Dir fields (relative vs absolute paths)
- Explain defu merge order and .reverse() trick in graphql-config virtual module
- Add BASE_SCHEMA_DEF purpose comment at both route handler call sites
- Replace '<directives>' magic string with descriptive '<inline-directives>'

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Replace GenImport/IESMImport with ScannedResolver as single source of truth
- Rename config.ts → create-config.ts, defaults.ts, file-header.ts, file-scanner.ts, runtime-generator.ts
- Split watcher/index.ts → create-watcher.ts + barrel
- Split pubsub/index.ts → memory-pubsub.ts + barrel
- Fix BASE_SCHEMA import (was importing from wrong module)
- Remove dead GenImport type alias and stale comments

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant