Skip to content

Pass OpenAPI document to Context class constructor#1638

Open
Copilot wants to merge 17 commits intomainfrom
copilot/pass-openapi-document-to-context
Open

Pass OpenAPI document to Context class constructor#1638
Copilot wants to merge 17 commits intomainfrom
copilot/pass-openapi-document-to-context

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 3, 2026

Summary

The Context class in _.context.ts had no way to access the loaded OpenAPI document at construction time. This change passes it as openApiDocument in the constructor options object, alongside the existing loadContext and readJson helpers. When the OpenAPI spec changes on disk, the document is reloaded and the Context automatically receives the latest spec via a stable read-only Proxy.

// routes/_.context.ts
export class Context {
  constructor({ openApiDocument, loadContext, readJson }) {
    this.spec = openApiDocument; // stays up-to-date as the spec changes on disk
  }
}
Original Prompt

Pass the OpenAPI document to the constructor of the Context class (exported from _.context.ts).

  1. Pass the openApiDocument object to ModuleLoader in app.ts
  2. Pass it down to the context object in module-loader.ts.

Manual acceptance tests

  • _.context.ts constructor receives openApiDocument and it matches the loaded spec (e.g. openApiDocument.paths contains the routes defined in the YAML)
  • Existing _.context.ts files with no constructor argument continue to work unchanged
  • loadContext and readJson still work correctly alongside openApiDocument
  • When no OpenAPI document is provided (e.g. openApiPath === "_"), openApiDocument is an empty object — not undefined or an error
  • After editing the OpenAPI spec file while the server is running, a Context that stored openApiDocument reflects the updated spec through the same proxy reference
  • Attempting to set or delete properties on openApiDocument inside a Context does not throw and has no effect (read-only)
  • Calling setOpenApiDocument even when no initial document was provided correctly populates the proxy

Tasks

  • Added openApiDocument: OpenApiDocument = {} parameter (defaulting to empty object) to ModuleLoader constructor
  • Imported OpenApiDocument type from dispatcher.ts into module-loader.ts
  • Stored the document directly in openApiDocumentRef (no .current wrapper); the Proxy captures self = this in its handler closures so reads always reflect the current value of this.openApiDocumentRef
  • setOpenApiDocument(newDoc) simply reassigns this.openApiDocumentRef = newDoc — no mutation of the document object, no early-return guard; always works even when no initial document was provided
  • The read-only Proxy (this.openApiDocumentProxy) is always created (even when no document is provided) and returns false for any set or deleteProperty traps
  • Passed this.openApiDocumentProxy into the Context constructor options object
  • In app.ts, counterfact() loads openApiDocument conditionally (openApiPath === "_" ? undefined : loadOpenApiDocument(...)) and shares it with both Dispatcher and ModuleLoader
  • Updated createMswHandlers() in app.ts to forward openApiDocument to ModuleLoader
  • In app.ts, added a codeGenerator "generate" event listener that reloads the spec on disk change and calls moduleLoader.setOpenApiDocument(newDoc) + updates dispatcher.openApiDocument
  • Resolved merge conflict markers in src/server/module-loader.ts and src/app.ts, combining the feature branch's Proxy/OpenApiDocument additions with main's FileDiscovery and middleware-detector additions
  • Made paths optional in the OpenApiDocument interface in dispatcher.ts so that {} is a valid default value (the for...in loop over paths in dispatcher.ts already handles undefined gracefully)
  • Added test: "passes openApiDocument to the Context constructor"
  • Added test: "defaults openApiDocument to an empty object when none is provided"
  • Added test: "setOpenApiDocument works even when no initial document was provided"
  • Added test: "updates the openApiDocument reference in-place when setOpenApiDocument is called"
  • Added minor changeset

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 3, 2026

🦋 Changeset detected

Latest commit: 7545c07

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
counterfact Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copilot AI changed the title [WIP] Pass the OpenAPI document to the constructor of the context class Pass OpenAPI document to Context class constructor Apr 3, 2026
Copilot AI requested a review from pmcelhaney April 3, 2026 23:00
Copy link
Copy Markdown
Collaborator

@pmcelhaney pmcelhaney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the document is changed on disk make sure the object passed to the context object receives the updates.

One way to do that is to remove all top level keys and replace them.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 3, 2026

If the document is changed on disk make sure the object passed to the context object receives the updates.

One way to do that is to remove all top level keys and replace them.

Done in commit 2cfdd55. ModuleLoader now has a setOpenApiDocument() method that removes all top-level keys from the stored document object and replaces them with the new document's keys — keeping the same object reference so existing Context instances see the update automatically.

In app.ts, the codeGenerator "generate" event (fired whenever the OpenAPI spec changes on disk) now reloads the document and calls moduleLoader.setOpenApiDocument(newDoc) + updates dispatcher.openApiDocument.

Copilot AI requested a review from pmcelhaney April 3, 2026 23:10
Copy link
Copy Markdown
Collaborator

@pmcelhaney pmcelhaney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be smarter to assign openApiDocument reference to a Proxy that is read only.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 3, 2026

Maybe it would be smarter to assign openApiDocument reference to a Proxy that is read only.

Done in commit 4c25b46. The openApiDocument passed to Context is now a read-only Proxy backed by a { current } ref. When setOpenApiDocument(newDoc) is called, it just swaps ref.current = newDoc — no mutation of the document object itself. All reads through the Proxy automatically reflect the latest document, and any attempt to set or delete properties returns false.

Copilot AI requested a review from pmcelhaney April 3, 2026 23:41
Copy link
Copy Markdown
Collaborator

@pmcelhaney pmcelhaney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .current property seems unnecessary. The object could just be assigned to openApiDocumentRef.

I don't understand why you wouldn't update the openApiDocument if it wasn't assigned originally. Make openApiDocumentRef default to an empty object if necessary.

If there are good reasons for either of these decisions, don't make changes. Just explain the reasoning.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 4, 2026

The .current property seems unnecessary. The object could just be assigned to openApiDocumentRef.

I don't understand why you wouldn't update the openApiDocument if it wasn't assigned originally. Mak...

Done in commit a20b89c.

  • Dropped the .current wrapper — openApiDocumentRef now holds the document directly. The Proxy captures self = this in its handler closures, so every read goes through self.openApiDocumentRef and picks up any reassignment automatically.
  • openApiDocumentRef defaults to {} when no document is provided, so the Proxy is always created and setOpenApiDocument always works without any early-return guard.

Copilot AI requested a review from pmcelhaney April 4, 2026 00:27
@pmcelhaney pmcelhaney marked this pull request as ready for review April 4, 2026 01:06
@pmcelhaney pmcelhaney enabled auto-merge April 4, 2026 01:06
@coveralls
Copy link
Copy Markdown
Collaborator

coveralls commented Apr 4, 2026

Coverage Report for PR #1638

Coverage decreased (-0.1%) to 87.212%

Diff Coverage: 36 of 54 changed lines covered (66.67%)

Uncovered Changes

File Covered Changed %
src/server/module-loader.ts 35 41 85.37%
src/app.ts 1 13 7.69%

Coverage Regressions

1 previously-covered line in 1 file lost coverage.

File Lines Lost Coverage
src/app.ts 1 62.9%

Coverage Status
Change from base Build 24049378993: -0.1%
Covered Lines: 6263
Relevant Lines: 7165

💛 - Coveralls

@pmcelhaney
Copy link
Copy Markdown
Collaborator

@copilot clean up the merge conflict mess

auto-merge was automatically disabled April 6, 2026 20:29

Head branch was pushed to by a user without write access

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 6, 2026

@copilot clean up the merge conflict mess

Done in commit f9d7dc7. Resolved the leftover conflict markers in both files:

  • module-loader.ts: merged our OpenApiDocument import + Proxy setup with main's FileDiscovery import and this.fileDiscovery initialization
  • app.ts: kept main's conditional openApiPath === "_" ? undefined : loadOpenApiDocument(...)

Copilot AI requested a review from pmcelhaney April 6, 2026 20:30
@pmcelhaney
Copy link
Copy Markdown
Collaborator

@copilot TypeScript compilation is failing

src/server/module-loader.ts:53:5 - error TS2741: Property 'paths' is missing in type '{}' but required in type 'OpenApiDocument'.

53 openApiDocument: OpenApiDocument = {},
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

src/server/dispatcher.ts:60:3
60 paths: {
~~~~~
'paths' is declared here.

Found 1 error in src/server/module-loader.ts:53

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 6, 2026

@copilot TypeScript compilation is failing

...

Fixed in commit 05a3c31. Made paths optional in the OpenApiDocument interface in dispatcher.ts — an empty {} is semantically valid when no spec is loaded, and the for...in loop in dispatcher.ts already handles undefined paths gracefully.

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.

Pass the OpenAPI document to the constructor of the context class

3 participants