Skip to content

feat(language-server): lazy document state via encapsulated artifact store + pull diagnostics - #887

Merged
SevInf merged 32 commits into
mainfrom
lsp-document-state
Jul 9, 2026
Merged

feat(language-server): lazy document state via encapsulated artifact store + pull diagnostics#887
SevInf merged 32 commits into
mainfrom
lsp-document-state

Conversation

@SevInf

@SevInf SevInf commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Reworks the language server's document-state lifecycle from eager, defensive reparse to invalidate-on-change + lazily materialize-on-read, synchronously, and moves diagnostics from push to LSP 3.17 pull (textDocument/diagnostic) with a capability-gated push fallback.

didOpen /
didChange  ──▶  artifacts.documentChanged(uri)      (domain event; no parse)
read req   ──▶  await project load (only while unresolved; config eval is async)
            ──▶  artifacts.document(uri)            (SYNC: parses internally iff needed)
            ──▶  derive (completion / diagnostics / tokens / folding)

Changes

  • Encapsulated artifact store (src/project-artifacts.ts): caching lives entirely inside ProjectArtifacts, constructed per project load with the config's inputs, control stack, and a text provider over the TextDocuments mirror. Reads parse internally exactly when needed; the server raises only domain events — documentChanged(uri) on open/edit, documentClosed(uri) on close — and a config reload replaces the store outright. No caching vocabulary appears in server.ts; nothing outside the store can trigger a reparse except a text edit or a config change. An untouched read is a map hit with zero parsing; a read after an edit parses exactly once. currentDocumentArtifact — the per-request whole-document text compare + defensive reparse — is deleted; completion, semantic tokens, folding, and the pull handler all read through the store.
  • Project lifecycle mirrors open inputs (src/server.ts): the server manages only documents declared in the config's inputs (strays are never associated; a project loaded solely for a stray is dropped at resolution), and a project is dropped when its last open input closes. Invariant: a live project always has ≥1 open input — which makes the whole-project symbolTable(): SymbolTable total (no undefined, no fabricated empty-table fallback; a violated invariant throws loudly). Project state is one map of ManagedProject, a discriminated union of loading | loaded — the illegal states two parallel maps permitted are unrepresentable, and the union surfaced a previously implicit cross-map flag (hadLoadedProject, which decides whether a failed reload owes push clients a diagnostics clear).
  • Pull diagnostics with push fallback: when the client advertises textDocument.diagnostic, the server advertises diagnosticProvider and serves full reports through the store; didOpen/didChange become invalidate-only, and config/watched-file changes send workspace/diagnostic/refresh (gated on refreshSupport) instead of republishing. Clients without pull support keep the previous eager push behavior unchanged. Exactly one transport is ever active per client. Flags ship as { interFileDependencies: false, workspaceDiagnostics: false } with a scope comment: current single-input implementation, not a property of PSL; flips with the future multi-input symbol table.
  • Registration fixes: the config-watcher glob is built with pathe (LSP globs are /-separated; node:path broke it on Windows), and workspace-root resolution honors workspaceFolders ahead of the deprecated rootUri/rootPath.

Why

  • Every edit previously parsed twice (eager publish + currentDocumentArtifact's per-request reparse with an O(n) whole-text compare) to dodge a stale-buffer race the LSP didChange sync contract already rules out. Because the vscode-languageserver runtime dispatches messages in order and the store handles mutation events synchronously against the already-updated mirror, stored artifacts can never outlive a mutation that affects them — no versions, no snapshots, no defensive compares.
  • Encapsulating the cache makes the invalidation surface auditable: the store's constructor and two event methods are the complete list of ways derived state can change, so a future mutation source (the multi-input symbol table) extends the store instead of adding scattered cache hygiene to handlers.
  • Tying project lifetime to open inputs converts "when is the symbol table absent?" from a documented edge case into an impossible state. Prior art surveyed line-by-line: tsserver retains projects with mark-and-sweep hysteresis because it keeps whole type-checked programs warm; we retain only a parsed config, so truthful types won over a cheap cache.
  • Dropping eager compute on change is only safe once diagnostics are pull-served, which is why lifecycle and transport land together.

Validation

  • Package gates: pnpm --filter @prisma-next/language-server test (198 tests: no-reparse-while-clean, one-parse edit-then-complete, reparse-after-config-reload, lifecycle drop/retain/no-resurrection, sibling-input symbol-table rebuild, pull/push capability gating, refresh semantics, workspaceFolders root resolution), typecheck, lint; playground typecheck/lint.
  • Workspace: pnpm build, pnpm typecheck, pnpm lint:deps, pnpm lint:framework-vocabulary (ratchet tightened 906→905), pnpm lint:casts (delta 0).
  • Manual QA: headless end-to-end run over the real playground WebSocket bridge (pull report with parse error, zero publishDiagnostics to the pull client, post-edit pull correct, tokens/folding healthy). Visual Monaco-marker check pending a browser run.

Non-goals

Removing TextDocuments; the multi-input project-wide symbol table (and interFileDependencies: true / workspace/diagnostic, gated on it); multi-project membership; behavior changes to completion/semantic-tokens/folding beyond their data source; watcher architecture / config-freshness mechanisms beyond the registration fixes above.

Summary by CodeRabbit

  • New Features

    • Added support for pull-based diagnostics alongside the existing diagnostic flow.
    • Improved language server handling for completion, semantic tokens, folding, and document updates with more reliable on-demand parsing.
  • Bug Fixes

    • Reduced unnecessary reparsing and made cached results more consistent across edits, config changes, and file closure.
    • Improved project detection so files outside configured inputs are handled more safely.
  • Tests

    • Expanded coverage for diagnostics, caching, project lifecycle, schema input handling, and configuration reload behavior.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: ac0d0166-e079-42c8-9c49-d8a53934f915

📥 Commits

Reviewing files that changed from the base of the PR and between f44cbcb and d243791.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • packages/1-framework/3-tooling/language-server/README.md
  • packages/1-framework/3-tooling/language-server/package.json
  • packages/1-framework/3-tooling/language-server/src/completion-provider.ts
  • packages/1-framework/3-tooling/language-server/src/project-artifacts.ts
  • packages/1-framework/3-tooling/language-server/src/schema-inputs.ts
  • packages/1-framework/3-tooling/language-server/src/semantic-tokens.ts
  • packages/1-framework/3-tooling/language-server/src/server.ts
  • packages/1-framework/3-tooling/language-server/test/config-resolution.test.ts
  • packages/1-framework/3-tooling/language-server/test/project-artifacts.test.ts
  • packages/1-framework/3-tooling/language-server/test/schema-inputs.test.ts
  • packages/1-framework/3-tooling/language-server/test/server.test.ts
  • scripts/lint-framework-vocabulary.config.json

📝 Walkthrough

Walkthrough

This PR refactors the language server's diagnostics architecture to support LSP 3.17 pull diagnostics alongside push fallback. The project artifact store is rewritten from an edit-driven cache to a read-through model keyed by getText. Symbol tables become required (non-optional) in completion and semantic token sources. Adds a uris() method to SchemaInputSet, introduces a pathe dependency, updates tests extensively, and adjusts a lint threshold.

Changes

Language Server Diagnostics and Artifact Refactor

Layer / File(s) Summary
Required symbol tables and URI enumeration
src/completion-provider.ts, src/semantic-tokens.ts, src/schema-inputs.ts, test/schema-inputs.test.ts
symbolTable becomes required (non-optional) in PslCompletionCandidateSource and SemanticTokenSource, removing undefined guards; SchemaInputSet gains a uris() method with tests.
Read-through project artifacts store
src/project-artifacts.ts, test/project-artifacts.test.ts
Replaces cached update/remove API with document(), symbolTable(), documentChanged(), documentClosed() backed by lazy getText-driven reads and invalidation.
Server managed-project lifecycle and pull/push diagnostics
src/server.ts
Introduces ManagedProject loading/loaded states, ResolvedClientCapabilities, conditional diagnostic capability advertisement, a pull-diagnostics handler, config-change refresh logic, and reads artifacts directly for completion/semantic tokens/folding.
Test harness and coverage expansion
test/server.test.ts, test/config-resolution.test.ts
Adds publishCount, diagnosticRefreshCount, pipeline-run spying, folding/pull-diagnostics helpers, and new tests for pull diagnostics, lazy parsing, and reload behavior; switches to pathe.
Docs, dependency, and lint config
README.md, package.json, scripts/lint-framework-vocabulary.config.json
Updates README diagnostics/artifact lifecycle description, adds pathe dependency, and lowers the framework vocabulary lint threshold.

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related PRs

  • prisma/prisma-next#852: Introduced the language-server runtime with src/server.ts that this PR refactors for pull diagnostics.
  • prisma/prisma-next#862: Overlapping work on the project-artifacts/server diagnostics lifecycle and pipeline integration.
  • prisma/prisma-next#878: Related changes to the same semantic-tokens implementation and server token wiring.

Suggested reviewers: wmadden

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main changes: lazy document state, an encapsulated artifact store, and pull diagnostics.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lsp-document-state

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@SevInf
SevInf force-pushed the lsp-autocomplete branch from ee9c443 to 82b8f4e Compare July 3, 2026 09:29
Base automatically changed from lsp-autocomplete to main July 3, 2026 15:06
@SevInf
SevInf force-pushed the lsp-document-state branch from bc000fb to 2c5a078 Compare July 6, 2026 08:58
@SevInf SevInf changed the title docs(language-server): spec for lazy document state + pull diagnostics feat(language-server): lazy document state via ensureCurrent + pull diagnostics Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 149.75 KB (0%)
postgres / emit 127.19 KB (0%)
mongo / no-emit 98.36 KB (0%)
mongo / emit 89.39 KB (0%)
cf-worker / no-emit 176.55 KB (0%)
cf-worker / emit 151.7 KB (0%)

@pkg-pr-new

pkg-pr-new Bot commented Jul 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@887

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@887

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@887

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@887

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@887

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@887

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@887

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@887

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@887

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@887

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@887

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@887

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@887

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@887

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@887

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@887

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@887

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@887

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@887

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@887

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@887

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@887

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@887

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@887

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@887

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@887

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@887

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@887

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@887

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@887

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@887

@prisma-next/config-loader

npm i https://pkg.pr.new/@prisma-next/config-loader@887

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@887

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@887

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@887

prisma-next

npm i https://pkg.pr.new/prisma-next@887

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@887

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@887

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@887

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@887

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@887

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@887

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@887

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@887

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@887

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@887

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@887

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@887

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@887

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@887

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@887

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@887

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@887

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@887

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@887

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@887

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@887

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@887

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@887

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@887

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@887

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@887

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@887

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@887

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@887

commit: d243791

Comment thread packages/1-framework/3-tooling/language-server/src/project-artifacts.ts Outdated
Comment thread packages/1-framework/3-tooling/language-server/src/server.ts Outdated
Comment thread packages/1-framework/3-tooling/language-server/src/server.ts Outdated
Comment thread packages/1-framework/3-tooling/language-server/src/server.ts Outdated
SevInf added 17 commits July 8, 2026 15:27
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…eCurrent seam

Key the per-project document cache on TextDocument.version: update() skips
recompute at an unchanged version, ensureCurrent(project, uri) is the single
materialize-on-read seam for completion, semantic tokens, and folding, and
the per-request whole-text compare (currentDocumentArtifact) is gone.
Config reloads invalidate cached versions so the next read recomputes
against the new stack. Push diagnostics on didChange are unchanged.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Advertise diagnosticProvider ({ interFileDependencies: false,
workspaceDiagnostics: false } — single-input implementation scope, to flip
with the future multi-input symbol table) when the client supports
textDocument/diagnostic, and serve full reports through the ensureCurrent
seam via a project-scoped report builder that can carry relatedDocuments
later. For pull clients didOpen/didChange become invalidate-only and config
changes request workspace/diagnostic/refresh (gated on refreshSupport)
instead of republishing; push clients keep the previous eager publish
behavior. Exactly one transport is ever active per client.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Section 2 still claimed a reparse on every change, contradicting the
pull-diagnostics description: parsing happens at most once per document
version, when a read triggers materialization.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…n report, and trace

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The mutation points are fully enumerable (didOpen, didChange, config
reload), so staleness detection moves to write time: each mutation evicts
the document cache entry (didOpen/didChange via remove, config reload via
clear) and reads parse on miss. Presence in the cache is currency — LSP
messages are dispatched in order and the notification handlers evict
synchronously against the already-updated text mirror. CachedDocument
loses its version field, update becomes materialize with an entry-present
fast path, and the -1 sentinel disappears.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Operator decision: the mutation points (didOpen, didChange, config reload)
are enumerable, so staleness moves to write-time eviction; version keying
was redundant. Trace updated for the D3 dispatch.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The store is constructed per project load with the config inputs, control
stack, and a text provider over the TextDocuments mirror; reads
(document(uri), symbolTable()) parse internally when needed and the only
externally visible mutations are the domain events documentChanged and
documentClosed plus store replacement on config reload. server.ts speaks
no caching vocabulary: ensureCurrent is gone, reads go straight to the
store, and open/change/close handlers raise events. CachedDocument is
renamed DocumentArtifacts as it crosses the package-internal boundary.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…tore

Operator decision: caching is fully encapsulated in ProjectArtifacts —
constructor-injected inputs/control stack/text provider, domain events
(document changed/closed, store replacement on config reload), lazy
internal parsing on read, and no caching vocabulary in server.ts.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Drop comments that restate signatures or narrate visible handler flow,
compress the single-input and report-builder rationales, and correct the
store contract note: in-order message dispatch is a vscode-languageserver
runtime property, not an LSP protocol guarantee. The capability-flags
scope comment stays verbatim as the project spec mandates it.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ingle resolution boundary

Document when ProjectArtifacts.document/symbolTable return undefined; fold
the four clientSupports* booleans into one ResolvedClientCapabilities
object resolved at initialize; drop the pull-handler capability guard
(non-pull clients are never advertised diagnosticProvider, so only a
protocol-violating client could reach it); and make
resolveProjectForDocument swallow config-discovery failures at its own
boundary so every call site reads as a plain undefined check instead of
repeating try/catch.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
symbolTable() no longer peeks: when unset it reads the first configured
input open in the text mirror through the same internal path document()
uses, so its undefined shrinks to exactly one case — no configured input
open. SchemaInputSet gains uris() to enumerate configured inputs; picking
the first open one preserves the single-input reality (multi-input
merging stays deferred).

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…tion

Every parse already produces a table, so each document read carries its
own: ProjectArtifacts loses symbolTable() (and the walk-the-inputs
materialization plus the shared table slot it needed), and consumers take
the table from the artifacts they already read. SemanticTokenSource and
PslCompletionCandidateSource become non-optional, dropping their internal
undefined guards. SchemaInputSet.uris(), added only for the deleted walk,
goes with it. getProjectSymbolTable keeps | undefined at the accessor
boundary because the document itself may be closed or a non-input.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
SevInf added 9 commits July 8, 2026 15:27
resolveProjectForDocument now validates membership after the nearest
config resolves: a document that is not one of the declared inputs keeps
no documentConfigPaths association and resolves to no project, so reads
and events never reach the artifact store for it and push clients receive
no publishes for stray files. A previously managed document that a config
reload drops from the inputs gets one clearing publish on the republish
path before becoming unmanaged. The explicit inputs check in
formatDocument goes as redundant.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…symbol table

A live project now always has at least one open input: didClose drops the
project once no association remains, loads that settle after the last
association vanished do not register (and clear any stale entry), and the
watched-files handler refreshes only live or loading projects while new or
fixed configs are still discovered lazily per document. On that invariant
the symbol table returns to ProjectArtifacts as symbolTable(): SymbolTable
with materialize-on-demand over the configured inputs (SchemaInputSet
regains uris()); a degenerate empty table doubles as the unset sentinel
and the type-totality safety net for the unreachable no-open-input case.
DocumentArtifacts loses its per-document table from the overruled design.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The membership-failure path in resolveProjectForDocument now performs the
same no-managed-documents drop the close handler does (shared helper), so
a project a stray document alone caused to load does not outlive the
resolution. A sibling open input keeps the project alive, and the
register-or-delete load-settle logic composes unchanged.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…e in slice spec

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… resolution

join from node:path yields backslash separators on Windows, which LSP
glob syntax never matches — the config watcher was dead there; pathe is
the repo convention and normalizes to forward slashes. Root resolution
now prefers workspaceFolders (first folder; multi-root out of scope) over
the deprecated rootUri/rootPath, so clients that send only
workspaceFolders watch the right tree instead of process.cwd(). Test
imports switched to pathe alongside per the path rule.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… exposed

The degenerate empty symbol table is gone: serving fabricated emptiness
would mask an invariant violation as missing completions, so the walk now
throws naming the broken invariant (unreachable while the server drops
projects whose last input closes). The throw immediately caught a real
gap: after the contributing document closed, a cache-hit read of a
sibling input never refilled the table slot — the walk now rebuilds from
the cached artifacts via a reusable pipeline symbol-table stage, without
reparsing. Framework-vocabulary comment lines rephrased to the symbolTable
identifier and the ratchet lowered to lock in 905.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
@SevInf
SevInf force-pushed the lsp-document-state branch from 7e78ca4 to a2765ce Compare July 8, 2026 15:43
Comment thread packages/1-framework/3-tooling/language-server/src/pipeline.ts Outdated
Comment thread packages/1-framework/3-tooling/language-server/src/project-artifacts.ts Outdated
Comment thread packages/1-framework/3-tooling/language-server/src/project-artifacts.ts Outdated
SevInf added 6 commits July 8, 2026 16:01
projects and projectLoads become a single map whose entry is either
loading (carrying the in-flight promise and whether a loaded project
preceded it) or loaded. Illegal states are unrepresentable: an entry can
no longer exist in both shapes, a settling load transitions only if it is
still the current entry and an association remains, and a failed load is
reaped by stopManagingProject in every awaiter with push clears decided
by the pre-load state. Reads during a config reload now await the fresh
resolution instead of the pre-reload project (operator-acknowledged
behavior shift, test-pinned); refresh chaining behind an in-flight load
is preserved.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
runSymbolTableStage added nothing over buildSymbolTable; both callers now
call it directly and pipeline.ts returns to its pre-refactor shape. The
symbolTable() walk uses early returns instead of nested undefined checks.
The framework-vocabulary ratchet stays at 905: the inlined destructure
adds one counted line, balanced by rewording the accessor comment to name
the symbolTable method it describes.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The chaining clause narrated code the previousLoad chain already shows,
and the settle comment restated the isCurrentLoad guard by name; both now
carry only their non-obvious rationale.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The slice is merge-ready; per the team close-out discipline the transient
project artifacts (spec, plan, slice spec, QA script and report, trace)
leave the repo with the project. The durable design rationale lives in the
language-server package (README and code) and in the PR narrative.

Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
@SevInf SevInf changed the title feat(language-server): lazy document state via ensureCurrent + pull diagnostics feat(language-server): lazy document state via encapsulated artifact store + pull diagnostics Jul 8, 2026
@SevInf
SevInf marked this pull request as ready for review July 9, 2026 09:07
@SevInf
SevInf requested a review from a team as a code owner July 9, 2026 09:07
@SevInf
SevInf merged commit 1dbc0af into main Jul 9, 2026
21 checks passed
@SevInf
SevInf deleted the lsp-document-state branch July 9, 2026 09:16
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