From 1c3e1abe2430ab4cf06ea8c6b41f19fa71820f91 Mon Sep 17 00:00:00 2001 From: Aleksandr Penskoi Date: Tue, 12 May 2026 18:15:51 +0200 Subject: [PATCH] TypeSchema: kind-key the TypeSchemaIndex slot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructures the index from `Record>` to `Record>>>` so multiple schemas can co-exist at the same url+package, discriminated by `identifier.kind`. No current consumer benefits — every slot still holds exactly one entry today — but this removes the URL/package collision that would otherwise block storing additional kind variants alongside their base. - append() now keys the slot by identifier.kind; duplicate check moves to the kind-keyed slot. - resolve() and resolveType() look up by id.kind, with a fallback to whatever's at the slot when the requested kind isn't present. The fallback preserves pre-refactor behavior: some IRs (e.g. CDA) declare dependency identifiers with a different `kind` than the actual stored schema, and the old slot-only lookup tolerated that. - resolveByUrl picks the first entry from the slot (single kind today; caller has no kind to disambiguate). --- src/typeschema/utils.ts | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/typeschema/utils.ts b/src/typeschema/utils.ts index a9e45eda..220adcf9 100644 --- a/src/typeschema/utils.ts +++ b/src/typeschema/utils.ts @@ -293,7 +293,7 @@ const populateGeneric = ( // Type Schema Index export type TypeSchemaIndex = { - _schemaIndex: Record>; + _schemaIndex: Record>>>; schemas: TypeSchema[]; schemasByPackage: Record; register?: Register; @@ -335,20 +335,22 @@ export const mkTypeSchemaIndex = ( irReport?: IrReport; }, ): TypeSchemaIndex => { - const index: Record> = {}; + const index: Record>>> = {}; const nestedIndex: Record> = {}; const append = (schema: TypeSchema) => { const url = schema.identifier.url; const pkg = schema.identifier.package; - if (!index[url]) index[url] = {}; + const kind = schema.identifier.kind; + const byPkg = (index[url] ??= {}); + const byKind = (byPkg[pkg] ??= {}); - if (index[url][pkg] && pkg !== "shared") { + if (byKind[kind] && pkg !== "shared") { const r1 = JSON.stringify(schema.identifier, undefined, 2); - const r2 = JSON.stringify(index[url][pkg]?.identifier, undefined, 2); + const r2 = JSON.stringify(byKind[kind]?.identifier, undefined, 2); if (r1 !== r2) throw new Error(`Duplicate schema: ${r1} and ${r2}`); return; } - index[url][pkg] = schema; + byKind[kind] = schema; if (isSpecializationTypeSchema(schema) || isProfileTypeSchema(schema)) { if (schema.nested) { @@ -366,12 +368,26 @@ export const mkTypeSchemaIndex = ( } populateTypeFamily(schemas); - const resolve = (id: Identifier): TypeSchema | undefined => { - return index[id.url]?.[id.package]; + /** Pick the single schema from a kind-keyed slot. resolveByUrl callers don't supply a + * kind, so they get whatever's there — fine while each slot holds at most one entry. */ + const pickFromSlot = ( + slot: Partial> | undefined, + ): TypeSchema | undefined => (slot ? Object.values(slot)[0] : undefined); + + /** Look up at [url][package][kind]; if the requested kind isn't present, fall back to + * whatever's at the slot. Some IRs (e.g. CDA) declare dependency identifiers with a + * different `kind` than the actual stored schema — the pre-kind-keyed index ignored + * `kind` entirely, so we preserve that lenience. */ + const lookup = (url: CanonicalUrl, pkg: PkgName, kind: TypeIdentifier["kind"]): TypeSchema | undefined => { + const slot = index[url]?.[pkg]; + if (!slot) return undefined; + return slot[kind] ?? pickFromSlot(slot); }; + + const resolve = (id: Identifier): TypeSchema | undefined => lookup(id.url, id.package, id.kind); const resolveType = (id: TypeIdentifier): TypeSchema | NestedTypeSchema | undefined => { if (isNestedIdentifier(id)) return nestedIndex[id.url]?.[id.package]; - return index[id.url]?.[id.package]; + return lookup(id.url, id.package, id.kind); }; populateGeneric(schemas, resolveType); @@ -380,10 +396,11 @@ export const mkTypeSchemaIndex = ( const resolutionTree = register.resolutionTree(); const resolution = resolutionTree[pkgName]?.[url]?.[0]; if (resolution) { - return index[url]?.[resolution.pkg.name]; + return pickFromSlot(index[url]?.[resolution.pkg.name]); } } - if (index[url]?.[pkgName]) return index[url]?.[pkgName]; + const direct = pickFromSlot(index[url]?.[pkgName]); + if (direct) return direct; if (nestedIndex[url]?.[pkgName]) return nestedIndex[url]?.[pkgName]; logger?.dryWarn(`Type '${url}' not found in '${pkgName}'`); @@ -392,7 +409,7 @@ export const mkTypeSchemaIndex = ( const anyPkg = Object.keys(index[url])[0]; if (anyPkg) { logger?.dryWarn(`Type '${url}' fallback to package ${anyPkg}`); - return index[url]?.[anyPkg]; + return pickFromSlot(index[url][anyPkg]); } } if (nestedIndex[url]) {