Making @globaltypesystem/gts-ts the single source of truth
Goal: let gts-ts own parsing, entity extraction, casting, and — most
importantly — schema/instance validation, so gts-kit stops maintaining its
own Ajv pipeline in packages/shared/src/registry.ts.
This document records what gts-ts already provides, what is missing, and the
exact API changes needed to complete the switch.
1. What gts-ts already provides (already used by gts-kit)
| Concern |
gts-ts API |
Used in gts-kit |
| GTS ID validate / parse / match / uuid |
isValidGtsID, validateGtsID, parseGtsID, matchIDPattern, idToUUID |
packages/shared/src/entities.ts |
| Schema-vs-instance detection + id/type extraction |
extractID → ExtractResult |
entities.ts (createEntity, applyGtsExtraction) |
| Registry / store |
GtsStore, createJsonEntity |
registry.ts (getGtsStore) |
| Modifier declaration errors (§9.11.1) |
GtsStore.register() throws |
registry.ts |
| Derivation / trait compatibility (§9.12 / §9.7) |
GtsStore.validateSchemaAgainstParent(id) |
registry.ts |
x-gts-ref schema + instance assertions (§9.6) |
XGtsRefValidator |
registry.ts |
| Abstract-type guard (§9.11.3) |
GtsModifiers.isAbstract, checkInstanceRules |
registry.ts |
| Cast (OP#9) |
castInstance, castInstanceRaw |
available |
| Compatibility / query / relationships / attributes |
checkCompatibility, query, resolveRelationships, getAttribute |
available |
So gts-kit already delegates identity, extraction, and the GTS-specific
structural rules. The remaining thing it does itself is the actual JSON
Schema validation of instances and schemas (its own Ajv in
registry.ts::createAjvInstance).
2. Why gts-kit still keeps its own Ajv (the gaps)
Gap A — Formats are disabled, with no opt-in
GtsStore builds Ajv with validateFormats: false and never registers
ajv-formats:
// node_modules/@globaltypesystem/gts-ts/dist/store.js
this.ajv = new Ajv({ strict:false, validateSchema:false,
validateFormats: false }); // "match Go lenient validation"
The config type only exposes { validateRefs, strictMode } — there is no
switch to enable formats. As a result date-time, email, uuid, uri,
ipv4/6, hostname, regex are not enforced at all through gts-ts.
gts-kit needs strict formats — in particular RFC 3339 date/time (see the
OP#6 conformance table in .gts-spec/tests/test_op6_schema_validation.py).
Gap B — Errors are a single string, not structured
GtsStore.validateInstance returns ValidationResult:
interface ValidationResult { id: string; ok: boolean; valid?: boolean; error: string }
error is a joined string ("${instancePath} ${message}; ..."). The VS
Code extension needs an array of structured errors to place squigglies and
specialise messages. It relies on this shape
(packages/shared/src/entities.ts::ValidationError):
interface ValidationError {
instancePath: string // "/dateTimeValue"
schemaPath: string // "#/properties/dateTimeValue/format"
keyword: string // "format" | "required" | "additionalProperties" | ...
message: string
params: Record<string, any> // e.g. { format }, { missingProperty }, { additionalProperty }
data?: any
}
See apps/vscode-extension/src/validation.ts (findErrorPosition,
findValueRangeAtInstancePath) — every branch keys off instancePath,
keyword, and params.
Gap C — No schema meta-validation with structured errors
gts-kit validates the schema document itself as JSON Schema
(compileAsync(schema.content)) and surfaces per-error paths. gts-ts's
register() only throws for §9.11.1 declaration errors; there is no
"meta-validate this schema and return structured errors" entry point.
Gap D — Synchronous compile / $ref resolution
validateInstance uses this.ajv.compile (sync). gts-kit uses compileAsync
with a loadSchema that resolves GTS IDs and the json-schema.org meta-schema.
Cross-schema $$ref/gts:// references are not guaranteed to resolve the same
way under sync compile.
Gap E — Parsing lives outside gts-ts
gts-ts consumes already-parsed JS objects (register(content),
createJsonEntity(content)). JSONC/YAML parsing, comments, trailing commas, and
.jsonc/.yaml selection live in @gts/shared (parse.ts, jsonc.ts,
yaml.ts). To have gts-ts "own parsing" it needs a text-in entry point.
3. Proposed gts-ts API changes
Priority order — A and B are blockers; C–E are follow-ups.
A. Enable standard + strict formats (blocker)
Add a config flag and bundle ajv-formats:
new GtsStore({ validateFormats: true }) // default true; false = legacy lenient
Requirements:
date-time, date, time must be strict RFC 3339:
date-time requires the T/t separator (reject the space form),
- requires a time-offset (
Z/z or ±HH:MM),
- validates real calendar/clock ranges (leap day, leap second
:60).
email, uuid, uri, ipv4, ipv6, hostname, regex (ECMA-262) enforced.
Prefer the standard ajv-formats validators over any hand-rolled regex.
gts-kit's interim fix (§5) shows the pattern: compose the library's own
fast and full format definitions (via addFormats.get(name, mode)) so a
value must satisfy both the strict grammar (fast) and the real value-range
checks (full). The same composition can live behind validateFormats: true
in gts-ts.
B. Structured validation errors (blocker)
Extend the result (keep error for back-compat, add an array):
interface ValidationIssue {
instancePath: string
schemaPath: string
keyword: string
message: string
params: Record<string, any>
data?: unknown
}
interface ValidationResult {
id: string
ok: boolean
valid?: boolean
error: string // keep: joined summary
errors?: ValidationIssue[] // NEW: structured, ~ raw Ajv ErrorObject[]
}
validateInstance / validateEntity should populate errors from
validate.errors (plus the abstract-type and x-gts-ref findings, each mapped
to an issue with its own instancePath/keyword).
C. Schema meta-validation
GtsStore.validateSchema(id: string): ValidationResult // structured `errors[]`
Compile the schema as JSON Schema and return structured meta-errors (what
gts-kit does today in registry.ts for JsonSchema entities).
D. Async, ref-resolving validate (optional)
GtsStore.validateInstanceAsync(id): Promise<ValidationResult>
Uses compileAsync + the store's loadSchema so cross-schema
$$ref/gts:// references resolve exactly like gts-kit's current loader.
E. File-text parsing entry point (optional)
parseGtsFile(fileName: string, text: string): { entities: JsonEntity[]; error?: string }
JSONC + YAML aware. (May reasonably stay in gts-kit; listed for completeness.)
4. gts-kit changes once the APIs land
In packages/shared/src/registry.ts:
- Delete
createAjvInstance, addFormats, matchesFormat, and the local
formatValidationErrors Ajv plumbing.
- In
validateEntity:
- schema entities →
store.validateSchema(id),
- instance entities →
store.validateInstance(id) (or the async variant),
- map
ValidationIssue[] straight onto ValidationError[] (shapes already
align).
- Keep using
GtsStore for §9.11.1 / derivation / traits / x-gts-ref (already
wired), now returned through the same structured errors[].
Net effect: one validation engine (gts-ts), consistent results across the CLI,
HTTP server, and the VS Code extension, and RFC 3339 behaviour owned by the
library.
5. Interim fix already applied in gts-kit
Until Gap A ships in gts-ts, registry.ts keeps its own Ajv but tightens the
temporal formats using only the standard ajv-formats validators — no
hand-rolled RFC logic. After addFormats(ajv), for date, time, and
date-time it composes the library's fast and full definitions
(addFormats.get(name, mode), evaluated by the matchesFormat helper) so a
value must satisfy both:
- the fast grammar — strict
T separator and a mandatory time-offset, and
- the full validator — real calendar/clock ranges (month, day, leap second,
offset ≤ 23:59).
This makes gts-kit reject the space form (e.g. "2008-10-12 10:30:00Z") and
every other OP#6 invalid format case while still accepting the valid ones. The
same composition moves into gts-ts under Gap A.
Making
@globaltypesystem/gts-tsthe single source of truthGoal: let gts-ts own parsing, entity extraction, casting, and — most
importantly — schema/instance validation, so gts-kit stops maintaining its
own Ajv pipeline in
packages/shared/src/registry.ts.This document records what gts-ts already provides, what is missing, and the
exact API changes needed to complete the switch.
1. What gts-ts already provides (already used by gts-kit)
isValidGtsID,validateGtsID,parseGtsID,matchIDPattern,idToUUIDpackages/shared/src/entities.tsextractID→ExtractResultentities.ts(createEntity,applyGtsExtraction)GtsStore,createJsonEntityregistry.ts(getGtsStore)GtsStore.register()throwsregistry.tsGtsStore.validateSchemaAgainstParent(id)registry.tsx-gts-refschema + instance assertions (§9.6)XGtsRefValidatorregistry.tsGtsModifiers.isAbstract,checkInstanceRulesregistry.tscastInstance,castInstanceRawcheckCompatibility,query,resolveRelationships,getAttributeSo gts-kit already delegates identity, extraction, and the GTS-specific
structural rules. The remaining thing it does itself is the actual JSON
Schema validation of instances and schemas (its own
Ajvinregistry.ts::createAjvInstance).2. Why gts-kit still keeps its own Ajv (the gaps)
Gap A — Formats are disabled, with no opt-in
GtsStorebuilds Ajv withvalidateFormats: falseand never registersajv-formats:The config type only exposes
{ validateRefs, strictMode }— there is noswitch to enable formats. As a result
date-time,email,uuid,uri,ipv4/6,hostname,regexare not enforced at all through gts-ts.gts-kit needs strict formats — in particular RFC 3339 date/time (see the
OP#6conformance table in.gts-spec/tests/test_op6_schema_validation.py).Gap B — Errors are a single string, not structured
GtsStore.validateInstancereturnsValidationResult:erroris a joined string ("${instancePath} ${message}; ..."). The VSCode extension needs an array of structured errors to place squigglies and
specialise messages. It relies on this shape
(
packages/shared/src/entities.ts::ValidationError):See
apps/vscode-extension/src/validation.ts(findErrorPosition,findValueRangeAtInstancePath) — every branch keys offinstancePath,keyword, andparams.Gap C — No schema meta-validation with structured errors
gts-kit validates the schema document itself as JSON Schema
(
compileAsync(schema.content)) and surfaces per-error paths. gts-ts'sregister()only throws for §9.11.1 declaration errors; there is no"meta-validate this schema and return structured errors" entry point.
Gap D — Synchronous compile /
$refresolutionvalidateInstanceusesthis.ajv.compile(sync). gts-kit usescompileAsyncwith a
loadSchemathat resolves GTS IDs and the json-schema.org meta-schema.Cross-schema
$$ref/gts://references are not guaranteed to resolve the sameway under sync compile.
Gap E — Parsing lives outside gts-ts
gts-ts consumes already-parsed JS objects (
register(content),createJsonEntity(content)). JSONC/YAML parsing, comments, trailing commas, and.jsonc/.yamlselection live in@gts/shared(parse.ts,jsonc.ts,yaml.ts). To have gts-ts "own parsing" it needs a text-in entry point.3. Proposed gts-ts API changes
Priority order — A and B are blockers; C–E are follow-ups.
A. Enable standard + strict formats (blocker)
Add a config flag and bundle
ajv-formats:Requirements:
date-time,date,timemust be strict RFC 3339:date-timerequires theT/tseparator (reject the space form),Z/zor±HH:MM),:60).email,uuid,uri,ipv4,ipv6,hostname,regex(ECMA-262) enforced.B. Structured validation errors (blocker)
Extend the result (keep
errorfor back-compat, add an array):validateInstance/validateEntityshould populateerrorsfromvalidate.errors(plus the abstract-type andx-gts-reffindings, each mappedto an issue with its own
instancePath/keyword).C. Schema meta-validation
Compile the schema as JSON Schema and return structured meta-errors (what
gts-kit does today in
registry.tsforJsonSchemaentities).D. Async, ref-resolving validate (optional)
Uses
compileAsync+ the store'sloadSchemaso cross-schema$$ref/gts://references resolve exactly like gts-kit's current loader.E. File-text parsing entry point (optional)
JSONC + YAML aware. (May reasonably stay in gts-kit; listed for completeness.)
4. gts-kit changes once the APIs land
In
packages/shared/src/registry.ts:createAjvInstance,addFormats,matchesFormat, and the localformatValidationErrorsAjv plumbing.validateEntity:store.validateSchema(id),store.validateInstance(id)(or the async variant),ValidationIssue[]straight ontoValidationError[](shapes alreadyalign).
GtsStorefor §9.11.1 / derivation / traits /x-gts-ref(alreadywired), now returned through the same structured
errors[].Net effect: one validation engine (gts-ts), consistent results across the CLI,
HTTP server, and the VS Code extension, and RFC 3339 behaviour owned by the
library.
5. Interim fix already applied in gts-kit
Until Gap A ships in gts-ts,
registry.tskeeps its own Ajv but tightens thetemporal formats using only the standard
ajv-formatsvalidators — nohand-rolled RFC logic. After
addFormats(ajv), fordate,time, anddate-timeit composes the library'sfastandfulldefinitions(
addFormats.get(name, mode), evaluated by thematchesFormathelper) so avalue must satisfy both:
Tseparator and a mandatory time-offset, andoffset ≤ 23:59).
This makes gts-kit reject the space form (e.g.
"2008-10-12 10:30:00Z") andevery other
OP#6invalid format case while still accepting the valid ones. Thesame composition moves into gts-ts under Gap A.