Skip to content

Coalesce concurrent requests for the same schema - #242

Merged
samlown merged 2 commits into
mainfrom
coalesce-schema-requests
Jul 20, 2026
Merged

Coalesce concurrent requests for the same schema#242
samlown merged 2 commits into
mainfrom
coalesce-schema-requests

Conversation

@samlown

@samlown samlown commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes schemas being fetched two or three times on editor mount, plus a related endpoint-reset issue. Two commits:

Coalesce concurrent requests for the same schema

The SchemaRegistry only caches a schema after its fetch resolves, so concurrent consumers — parallel branches of the form parser's parseSchema recursion plus the Monaco loadSchemaSet preload, all racing at mount — each missed the cache and issued their own request for the same URL.

fetchExternalSchema now keeps an in-flight map keyed by URL: the first caller starts the fetch and stores the promise, later callers await the same promise, and the entry is cleared on settle (so a failed fetch can be retried rather than caching the failure). loadSchemaSet's strict root fetch goes through the same map via a new fetchExternalSchemaStrict, preserving its throw-on-failure semantics while joining the coalescing.

The custom JSON worker's schemaRequestService is unaffected: it only runs in the enableSchemaRequest: true fallback, which the preloading path disables.

Keep the configured API endpoint when editors mount without one

apiBaseUrl defaulted to the public gobl.dev service, so any editor mounted without an explicit endpoint (e.g. a nested correct/headers modal) reset the shared client back to the default, undoing the embedder's configuration and sending stray requests to gobl.dev. The prop now defaults to an empty string, which setApiBaseUrl treats as a no-op, so editors without an explicit endpoint inherit whatever is already configured.

Testing

  • npm run check — 0 errors, 0 warnings.
  • npm run lint and npm run build:package clean.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings July 20, 2026 10:52
@netlify

netlify Bot commented Jul 20, 2026

Copy link
Copy Markdown

Deploy Preview for gobl-build ready!

Name Link
🔨 Latest commit 2c5b58c
🔍 Latest deploy log https://app.netlify.com/projects/gobl-build/deploys/6a5e1a49ea654b00089eb2c0
😎 Deploy Preview https://deploy-preview-242--gobl-build.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

The SchemaRegistry only caches a schema once its fetch resolves, so
consumers asking for the same schema while a request was still in
flight — parallel branches of the form parser plus the Monaco preload
at mount time — each missed the cache and issued their own fetch,
loading every schema two or three times.

fetchExternalSchema now tracks in-flight requests by URL and shares the
pending promise between callers, so each schema is fetched exactly once.
loadSchemaSet's strict root fetch joins the same map, keeping its
throw-on-failure semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@samlown
samlown force-pushed the coalesce-schema-requests branch from 1650451 to f95f5a6 Compare July 20, 2026 10:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR reduces duplicate JSON Schema network requests during editor mount by coalescing concurrent fetches for the same schema URL in the form schema loader, so parallel consumers share a single in-flight request.

Changes:

  • Add an in-flight promise cache (pendingSchemas) and a strict fetch helper to dedupe concurrent schema loads and preserve throw-on-root-load behavior.
  • Route loadSchemaSet root schema loading through the same strict/coalesced fetch path.
  • Adjust editor apiBaseUrl prop defaults/docs so nested editors don’t reset an embedder-configured endpoint.

Reviewed changes

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

File Description
src/lib/editor/form/utils/schema.ts Adds in-flight request coalescing and uses strict root fetching for schema preloads.
src/lib/types/editor.ts Updates apiBaseUrl prop documentation to reflect “inherit existing endpoint” behavior.
src/lib/ObjectEditor.svelte Stops defaulting apiBaseUrl to the global default to avoid nested editor resets.
src/lib/EnvelopeEditor.svelte Same apiBaseUrl default/inheritance change as ObjectEditor.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/editor/form/utils/schema.ts
Copilot AI review requested due to automatic review settings July 20, 2026 10:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/lib/editor/form/utils/schema.ts:46

  • pendingSchemas is a plain object keyed by arbitrary schema IDs/URLs. Using a normal object can lead to key-collision/prototype-pollution edge cases (e.g. __proto__, constructor) if a schema reference ever contains such a value. A Map avoids these hazards and is a better fit for an in-flight cache.
const pendingSchemas: Record<string, Promise<Schema>> = {}

function fetchExternalSchemaStrict(id: string): Promise<Schema> {
  const schema = SchemaRegistry[id]
  if (schema) return Promise.resolve(schema)

The apiBaseUrl prop defaulted to the public gobl.dev service, so any
editor mounted without an explicit endpoint — such as a nested correct
or headers modal — reset the shared client back to the default, undoing
the embedder's configuration and sending stray requests to gobl.dev.

Default the prop to an empty string instead, which setApiBaseUrl treats
as a no-op, so editors without an explicit endpoint inherit whatever is
already configured.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 20, 2026 12:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/lib/editor/form/utils/schema.ts:46

  • pendingSchemas is a plain object keyed by URL. Since these keys ultimately come from external schema URLs / $ref values, using a Record risks special keys like __proto__ / constructor causing prototype pollution and hard-to-debug behavior. Use a Map for the in-flight cache to avoid property-key hazards.
const pendingSchemas: Record<string, Promise<Schema>> = {}

function fetchExternalSchemaStrict(id: string): Promise<Schema> {
  const schema = SchemaRegistry[id]
  if (schema) return Promise.resolve(schema)

@samlown
samlown merged commit ba656cb into main Jul 20, 2026
7 checks passed
@samlown
samlown deleted the coalesce-schema-requests branch July 20, 2026 12:57
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.

2 participants