ParseContext
The Parse-phase context. Owns the in-progress parsed document (discriminated by protocol), accumulates parse issues, tracks $ref consumers for cascade pruning, and exposes the issue-logging surface…
Source
skmtc/deno/core/context/ParseContext.ts
Class
class ParseContext {
issues: ParseIssue[]
logger: Logger
silent: boolean
protocol: ProtocolState // discriminated union
#refConsumers: Map<string, StackTrail[]> // private
#refErrors: Map<string, unknown[]> // private
constructor(args: {
input: SkmtcDocumentInput
logger: Logger
silent?: boolean
options?: { gql?: GqlParseOptions }
})
// Parse-phase entry
parse(stackTrail: StackTrail): SkmtcParsedDocument
removeErroredItems(): void
// Ref tracking
registerRef(consumer: StackTrail, refKey: string): void
registerRefError(error: unknown, refKey: string | undefined): void
// Issue logging surface
logIssueAt(issue: LogIssueAtArgs, parent?: unknown): void
logIssue(args: LogIssueArgs): void
logIssueNoKey(args: LogIssueNoKeyArgs): void
log(args: LogAtArgs): void
logSkippedFields(args: LogSkippedValuesArgs): void
// Protocol-discriminated getters
get oasDocument(): OasDocument // OAS-only; throws on GQL
get documentObject(): OpenAPIV3.Document // OAS-only; throws on GQL
get schema(): GraphQLSchema // GQL-only; throws on OAS
get registry(): GqlRegistry // GQL-only; throws on OAS
get gqlDocument(): GqlDocument // GQL-only; throws on OAS
get parsedDocument(): SkmtcParsedDocument // either
}Constructor
new ParseContext({
input: SkmtcDocumentInput, // the discriminated document input
logger: Logger,
silent?: boolean, // suppress logger output; default true
options?: { gql?: GqlParseOptions }
})The constructor sets up protocol based on input.type:
- For
{ type: 'oas', value: OpenAPIV3.Document }: initializesprotocolasOasProtocolStatewith an emptyOasDocumentready to be populated. - For
{ type: 'gql', value: GraphQLSchema | string }: builds the schema (if string), initializesprotocolasGqlProtocolStatewith an emptyGqlDocument(the forward-reference target — populated byparse()).
Generators don't instantiate ParseContext — it's created
internally by toArtifacts.
Properties
issues: ParseIssue[]
The accumulating issue log. Per-item parse failures, type-inference warnings, unexpected-property warnings — all end up here.
Each issue:
type ParseIssue = {
protocol: 'oas' | 'gql'
level: 'error' | 'warning'
type: string // see reference/error-codes.md
location: string
message: string
cause?: unknown
}After parse, the issues are written into the manifest's
parseIssues field.
logger: Logger
Structured logger for trace-level output. Mirrored from
logIssueAt when silent is false.
silent: boolean
When true, issue-logging methods don't echo to the logger — they
only accumulate into issues. When false, the logger receives each
issue as a warn-level structured event.
Default: true (suppress chatter; the manifest is the canonical record).
protocol: ProtocolState
Discriminated union. Either:
type OasProtocolState = {
type: 'oas'
documentObject: OpenAPIV3.Document
oasDocument: OasDocument
}
type GqlProtocolState = {
type: 'gql'
schema: GraphQLSchema
registry: GqlRegistry
gqlDocument: GqlDocument
options: GqlParseOptions
}Most callers access protocol state through the typed getters
(oasDocument, gqlDocument, etc.) which throw on misroute.
#refConsumers: Map<string, StackTrail[]> (private)
Inverse index: "who pointed at this ref?" Populated by registerRef
during the parse walk. Used by removeErroredItems to identify
which items to prune when their referenced schemas failed.
#refErrors: Map<string, unknown[]> (private)
"What went wrong with this ref?" Populated by registerRefError
when a ref's target fails to parse. Cross-referenced with
#refConsumers during cascade pruning.
Methods
parse(stackTrail: StackTrail): SkmtcParsedDocument
The Parse-phase entry point. Walks the input document, builds the
parsed model, runs cascade pruning. Returns the discriminated
SkmtcParsedDocument.
Dispatches on protocol.type:
- OAS: calls
toDocumentFieldsV3to populateoasState.oasDocument.fields, thenremoveErroredItems. - GQL: calls
parseGqlDocumentto compute fields, populates the emptygqlDocumentissued at construction, thenremoveErroredItems.
Does not throw. All parse-time failures become issues in the
issues array. Worst case: the returned document has fewer items
than the input.
removeErroredItems(): void
The cascade-pruning step. Walks #refErrors and #refConsumers
together. For each failed ref, prunes every consumer from the
parsed document and logs an INVALID_DEPENDENCY_REF issue at the
consumer's location.
Cascade depth: one hop. Transitive consumers (consumers of
pruned items) aren't recursively pruned. They typically fail later
at generate time with Ref "..." not found.
registerRef(consumer: StackTrail, refKey: string): void
Called by parser code when a $ref is encountered. Records the
consumer (via cloned stack trail) under the ref key.
// In core/oas/ref/toRefV31.ts:26
context.registerRef(stackTrail.clone(), $ref)The refKey is typically the literal $ref string. The clone is
necessary because StackTrail is mutable; without cloning, the
stored value would change as the walk progresses.
registerRefError(error: unknown, refKey: string | undefined): void
Called when a parse failure occurs at a component position. Stores
the error against the ref key. undefined refKey is a deliberate
no-op (used when the failure is at a non-component position).
Auto-called by logIssueNoKey for issues at component locations.
Parser code rarely calls this directly.
logIssueAt(issue: LogIssueAtArgs, parent?: unknown): void
The universal issue recorder. Pushes to issues; mirrors to the
logger when silent is false.
type LogIssueAtArgs =
| { protocol: 'oas', level: 'error', type: OasIssueType, location, message, cause? }
| { protocol: 'oas', level: 'warning', type: OasIssueType, location, message }
| { protocol: 'gql', level: 'error', type: GqlIssueType, location, message, cause? }
| { protocol: 'gql', level: 'warning', type: GqlIssueType, location, message }parent is optional context for the logger payload (stringified for
clone-safety) — not stored on the issue itself.
logIssue({ key, parent, type, stackTrail, ...issue }): void
The OAS-flavored surface. Threads key through
stackTrail.trace(key, st => ...) to push a child trail, then
delegates to logIssueNoKey.
type LogIssueArgs = {
key: string
stackTrail: StackTrail
parent?: unknown
type: OasIssueType | GqlIssueType
level: 'error'
message: string
cause?: unknown
} | {
key: string
stackTrail: StackTrail
parent?: unknown
type: OasIssueType | GqlIssueType
level: 'warning'
message: string
}logIssueNoKey({ parent, type, stackTrail, ...issue }): void
Like logIssue but without pushing a stack-trail key. Used when
the parser is already at the right depth.
Auto-registers error-level issues against the ref-key derivable
from the stack trail (via StackTrail.toStackRef()). This is what
makes cascade pruning work — error issues at component positions
automatically populate #refErrors.
log({ location, parent, type, ...issue }): void
Convenience for logging an issue at a pre-computed string
location (e.g., a GraphQL Query.fieldName address). Constructs a
StackTrail from location.split(':') and delegates to
logIssueNoKey.
Useful when:
- The natural location isn't a tree position (e.g., schema-level directive definitions)
- The parser threw before producing an entity to attach to
logSkippedFields({ skipped, stackTrail, parent, parentType, type? }): void
Records one warning per unrecognized key under parent. Each
skipped key is traced as a child of stackTrail.
type defaults to UNEXPECTED_PROPERTY. GQL callers may pass a
more specific category.
// Typical use in OAS parsers
const { foo, bar, baz, ...skipped } = obj // skip known keys
context.logSkippedFields({
skipped,
parent: obj,
parentType: 'SchemaObject',
stackTrail
})Getters
The protocol-discriminated getters throw if accessed on the wrong protocol. This is deliberate — parser code that uses these is protocol-specific; a misroute is a real bug, not a recoverable situation.
oasDocument, documentObject (OAS-only)
get oasDocument(): OasDocument // throws on GQL
get documentObject(): OpenAPIV3.Document // throws on GQLoasDocument is the in-progress parsed OasDocument instance.
documentObject is the raw input (the OpenAPIV3.Document JSON).
schema, registry, gqlDocument (GQL-only)
get schema(): GraphQLSchema // throws on OAS
get registry(): GqlRegistry // throws on OAS
get gqlDocument(): GqlDocument // throws on OASschema is the graphql-js parsed schema. registry is the
SKMTC-side registry of GQL types. gqlDocument is the in-progress
parsed GqlDocument (populated by parse()).
parsedDocument (either)
get parsedDocument(): SkmtcParsedDocumentDiscriminated wrapper for the current protocol's parsed document.
Used when constructing OasRefs (which need a document reference
for resolution).
Examples
Logging an issue from a parser
// In core/oas/schema/toObject.ts (example pattern)
function toObject({ value, stackTrail, context }) {
if (!isValidObjectShape(value)) {
context.logIssueNoKey({
level: 'warning',
stackTrail,
parent: value,
message: 'Schema has "properties" but is missing type="object"',
type: 'MISSING_OBJECT_TYPE'
})
// ... proceed with inferred type
}
}Registering a ref consumer
// In core/oas/ref/toRefV31.ts
export const toRefV31 = ({ ref, refType, stackTrail, context }) => {
context.registerRef(stackTrail.clone(), ref.$ref)
return new OasRef({ refType, $ref: ref.$ref }, context.parsedDocument)
}The cloned stack trail captures where the ref was encountered. After parse, cascade pruning uses these consumer trails to find items to remove.
Per-item isolation with tryParseAt
tryParseAt is the wrapper that catches per-item parse failures.
Generators don't write this directly — it's used by parsers like
toSchemasV3:
for (const [key, schema] of entries) {
const value = tryParseAt({
stackTrail, key, context,
type: 'INVALID_SCHEMA',
parent: schema,
fn: st => toSchemaV3({ schema, stackTrail: st, context })
})
if (value !== undefined) output[key] = value
}On throw, tryParseAt logs an error issue and returns undefined;
the offending entry is omitted from output.
Common questions
Why does oasDocument throw on GQL contexts?
To prevent misrouted parser code. OAS-specific parsers shouldn't
run on GQL contexts; if they do, the throw makes the bug visible
immediately. Returning undefined would let the parser proceed
into undefined-behavior territory.
When is gqlDocument accessed during parse vs after?
During parse, gqlDocument is empty at construction time. The
empty instance is issued so OasRefs constructed during the walk
have a stable resolution target. parse() populates the empty
document at the end of the walk via
gqlState.gqlDocument.fields = fields.
After parse, the document is fully populated; refs resolve through the now-filled registry.
This is the forward-reference resolution trick described in refs-and-resolution.
Why does cascade pruning only go one hop?
Implementation complexity. Transitive pruning requires walking the
ref graph to a fixpoint, which is non-trivial (cycles, iteration
order, idempotency). Deferred. Transitive failures surface at
generate time as Ref not found exceptions, which the generator's
per-item try/catch handles gracefully.
Can I log issues from generator code (not parser code)?
Generators have context: GenerateContextType, not
ParseContextType. The GenerateContextType doesn't expose the
issue-logging surface — parse issues are parse-phase only. If a
generator detects something wrong, the right move is to throw
(caught by #runOasOperationGenerator, mark the item as
'error') rather than try to log a parseIssue.
Why is silent true by default?
The manifest is the canonical record; logger output is for
debugging. Suppressing logger output by default keeps generation
quiet for the common case. Set silent: false when debugging
parser internals to see structured warn events as they happen.
Related types
type SkmtcDocumentInput =
| { type: 'oas', value: OpenAPIV3.Document }
| { type: 'gql', value: GraphQLSchema | string }
type SkmtcParsedDocument =
| { type: 'oas', value: OasDocument }
| { type: 'gql', value: GqlDocument }
type GqlParseOptions = {
includeIntrospection?: boolean
// ... protocol-specific options
}See also
- Error handling philosophy concept — the two-tier model
- Refs and resolution concept — how cascade pruning works
- The three phases concept — pipeline context
- Error codes reference — parseIssue types
- API: GenerateContext — what runs after ParseContext
skmtc-debugskill — operational use of parseIssues
OAS schema variants
The union type OasSchema and its eight constituent classes: OasObject, OasArray, OasUnion, OasString, OasInteger, OasNumber, OasBoolean, OasUnknown. Sibling classes with a shared duck-typed interface…
Projection bases
The factory functions that build a generator's file-level Projection base class: toTsModelProjectionBase, toTsOasOperationProjectionBase, toTsGqlOperationProjectionBase (the authoring-facing veneers…