fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: add changelog

Lubos 47dd2a31 300d2311

+201 -184
+5
.changeset/quiet-hats-switch.md
··· 1 + --- 2 + "@hey-api/shared": patch 3 + --- 4 + 5 + **transform(read-write)**: improve discriminated schemas split
+196 -184
packages/shared/src/openApi/shared/transforms/readWrite.ts
··· 17 17 18 18 type OriginalSchemas = Record<string, unknown>; 19 19 20 + type SplitMapping = Record< 21 + string, 22 + { 23 + read?: string; 24 + write?: string; 25 + } 26 + >; 27 + 20 28 type SplitSchemas = { 21 29 /** Key is the original schema pointer. */ 22 - mapping: Record< 23 - string, 24 - { 25 - read?: string; 26 - write?: string; 27 - } 28 - >; 30 + mapping: SplitMapping; 29 31 /** splitPointer -> originalPointer */ 30 32 reverseMapping: Record<string, string>; 31 33 /** name -> schema object */ ··· 313 315 }; 314 316 315 317 /** 318 + * Create writable variants of parent schemas that have discriminators 319 + * and are referenced by split schemas. 320 + */ 321 + function splitDiscriminatorSchemas({ 322 + config, 323 + existingNames, 324 + schemasPointerNamespace, 325 + spec, 326 + split, 327 + }: { 328 + config: ReadWriteConfig; 329 + existingNames: Set<string>; 330 + schemasPointerNamespace: string; 331 + spec: unknown; 332 + split: SplitSchemas; 333 + }) { 334 + const schemasObj = getSchemasObject(spec); 335 + if (!schemasObj) return; 336 + 337 + const parentSchemasToSplit = new Map<string, Set<Scope>>(); 338 + 339 + // First pass: identify parent schemas that need writable variants 340 + for (const [name, schema] of Object.entries(split.schemas)) { 341 + const pointer = `${schemasPointerNamespace}${name}`; 342 + const originalPointer = split.reverseMapping[pointer]; 343 + 344 + if (originalPointer) { 345 + const mapping = split.mapping[originalPointer]; 346 + if (mapping) { 347 + const contextVariant: Scope | null = 348 + mapping.read === pointer ? 'read' : mapping.write === pointer ? 'write' : null; 349 + 350 + // Check allOf for $refs to schemas with discriminators 351 + if ( 352 + contextVariant && 353 + schema && 354 + typeof schema === 'object' && 355 + 'allOf' in schema && 356 + schema.allOf instanceof Array 357 + ) { 358 + for (const comp of schema.allOf) { 359 + if ( 360 + comp && 361 + typeof comp === 'object' && 362 + '$ref' in comp && 363 + typeof comp.$ref === 'string' 364 + ) { 365 + const refPath = jsonPointerToPath(comp.$ref); 366 + const schemaName = refPath[refPath.length - 1]; 367 + 368 + if (typeof schemaName === 'string' && schemaName in schemasObj) { 369 + const resolvedSchema = schemasObj[schemaName]; 370 + 371 + // Check if this schema has a discriminator with mapping 372 + if ( 373 + resolvedSchema && 374 + typeof resolvedSchema === 'object' && 375 + 'discriminator' in resolvedSchema && 376 + resolvedSchema.discriminator && 377 + typeof resolvedSchema.discriminator === 'object' && 378 + 'mapping' in resolvedSchema.discriminator && 379 + resolvedSchema.discriminator.mapping && 380 + typeof resolvedSchema.discriminator.mapping === 'object' 381 + ) { 382 + // This parent schema needs a variant for this context 383 + if (!parentSchemasToSplit.has(comp.$ref)) { 384 + parentSchemasToSplit.set(comp.$ref, new Set()); 385 + } 386 + parentSchemasToSplit.get(comp.$ref)!.add(contextVariant); 387 + } 388 + } 389 + } 390 + } 391 + } 392 + } 393 + } 394 + } 395 + 396 + // Second pass: create writable variants of parent schemas and update their discriminator mappings 397 + const parentSchemaVariants = new Map<string, SplitMapping[keyof SplitMapping]>(); 398 + 399 + for (const [parentRef, contexts] of parentSchemasToSplit) { 400 + const refPath = jsonPointerToPath(parentRef); 401 + const parentName = refPath[refPath.length - 1]; 402 + 403 + if (typeof parentName !== 'string' || !(parentName in schemasObj)) continue; 404 + 405 + const parentSchema = schemasObj[parentName]; 406 + if (!parentSchema || typeof parentSchema !== 'object') continue; 407 + 408 + const variants: SplitMapping[keyof SplitMapping] = {}; 409 + 410 + // Create variants for each context 411 + for (const context of contexts) { 412 + const variantSchema = deepClone(parentSchema); 413 + 414 + // Update discriminator mapping in the variant 415 + if ( 416 + 'discriminator' in variantSchema && 417 + variantSchema.discriminator && 418 + typeof variantSchema.discriminator === 'object' && 419 + 'mapping' in variantSchema.discriminator && 420 + variantSchema.discriminator.mapping && 421 + typeof variantSchema.discriminator.mapping === 'object' 422 + ) { 423 + const mapping = variantSchema.discriminator.mapping; 424 + const updatedMapping: Record<string, string> = {}; 425 + 426 + for (const [discriminatorValue, originalRef] of Object.entries(mapping)) { 427 + const map = split.mapping[originalRef]; 428 + if (map) { 429 + if (context === 'read' && map.read) { 430 + updatedMapping[discriminatorValue] = map.read; 431 + } else if (context === 'write' && map.write) { 432 + updatedMapping[discriminatorValue] = map.write; 433 + } else { 434 + updatedMapping[discriminatorValue] = originalRef; 435 + } 436 + } else { 437 + updatedMapping[discriminatorValue] = originalRef; 438 + } 439 + } 440 + 441 + variantSchema.discriminator.mapping = updatedMapping; 442 + } 443 + 444 + // Add the variant to split.schemas with an appropriate name 445 + if (context === 'write') { 446 + const writeBase = applyNaming(parentName, config.requests); 447 + const writeName = getUniqueComponentName({ 448 + base: writeBase, 449 + components: existingNames, 450 + }); 451 + existingNames.add(writeName); 452 + split.schemas[writeName] = variantSchema; 453 + variants.write = `${schemasPointerNamespace}${writeName}`; 454 + } 455 + // We could create read variants too, but typically they're not needed 456 + // since the original schema serves as the read variant 457 + } 458 + 459 + parentSchemaVariants.set(parentRef, variants); 460 + } 461 + 462 + // Third pass: update $refs in split schemas to point to the parent variants 463 + for (const [name, schema] of Object.entries(split.schemas)) { 464 + const pointer = `${schemasPointerNamespace}${name}`; 465 + const originalPointer = split.reverseMapping[pointer]; 466 + if (!originalPointer) continue; 467 + 468 + const mapping = split.mapping[originalPointer]; 469 + if (!mapping) continue; 470 + 471 + const contextVariant: Scope | null = 472 + mapping.read === pointer ? 'read' : mapping.write === pointer ? 'write' : null; 473 + 474 + if (contextVariant && schema && typeof schema === 'object') { 475 + // Update $refs in allOf 476 + if ('allOf' in schema && schema.allOf instanceof Array) { 477 + for (let i = 0; i < schema.allOf.length; i++) { 478 + const comp = schema.allOf[i]; 479 + 480 + if (comp && typeof comp === 'object' && '$ref' in comp && typeof comp.$ref === 'string') { 481 + const variants = parentSchemaVariants.get(comp.$ref); 482 + 483 + if (variants) { 484 + if (contextVariant === 'write' && variants.write) { 485 + comp.$ref = variants.write; 486 + } else if (contextVariant === 'read' && variants.read) { 487 + comp.$ref = variants.read; 488 + } 489 + } 490 + } 491 + } 492 + } 493 + } 494 + } 495 + } 496 + 497 + /** 316 498 * Splits schemas with both 'read' and 'write' scopes into read/write variants. 317 499 * Returns the new schemas and a mapping from original pointer to new variant pointers. 318 500 * ··· 437 619 split.reverseMapping[writePointer] = pointer; 438 620 } 439 621 440 - //Create writable variants of parent schemas that have discriminators and are referenced by split schemas 441 - const parentSchemasToSplit = new Map<string, Set<'read' | 'write'>>(); 442 - 443 - // First pass: identify parent schemas that need writable variants 444 - for (const [name, schema] of Object.entries(split.schemas)) { 445 - const pointer = `${schemasPointerNamespace}${name}`; 446 - const originalPointer = split.reverseMapping[pointer]; 447 - 448 - if (originalPointer) { 449 - const mapping = split.mapping[originalPointer]; 450 - if (mapping) { 451 - const contextVariant = 452 - mapping.read === pointer ? 'read' : mapping.write === pointer ? 'write' : null; 453 - 454 - if (contextVariant && schema && typeof schema === 'object') { 455 - // Check allOf for $refs to schemas with discriminators 456 - if ('allOf' in schema && Array.isArray((schema as Record<string, unknown>).allOf)) { 457 - const allOf = (schema as Record<string, unknown>).allOf as Array<unknown>; 458 - 459 - for (const comp of allOf) { 460 - if ( 461 - comp && 462 - typeof comp === 'object' && 463 - '$ref' in comp && 464 - typeof comp.$ref === 'string' 465 - ) { 466 - const ref = comp.$ref as string; 467 - const schemasObj = getSchemasObject(spec); 468 - 469 - if (schemasObj) { 470 - const refPath = jsonPointerToPath(ref); 471 - const schemaName = refPath[refPath.length - 1]; 472 - 473 - if (typeof schemaName === 'string' && schemaName in schemasObj) { 474 - const resolvedSchema = schemasObj[schemaName]; 475 - 476 - // Check if this schema has a discriminator with mapping 477 - if ( 478 - resolvedSchema && 479 - typeof resolvedSchema === 'object' && 480 - 'discriminator' in resolvedSchema && 481 - resolvedSchema.discriminator && 482 - typeof resolvedSchema.discriminator === 'object' && 483 - 'mapping' in resolvedSchema.discriminator && 484 - resolvedSchema.discriminator.mapping && 485 - typeof resolvedSchema.discriminator.mapping === 'object' 486 - ) { 487 - // This parent schema needs a variant for this context 488 - if (!parentSchemasToSplit.has(ref)) { 489 - parentSchemasToSplit.set(ref, new Set()); 490 - } 491 - parentSchemasToSplit.get(ref)!.add(contextVariant); 492 - } 493 - } 494 - } 495 - } 496 - } 497 - } 498 - } 499 - } 500 - } 501 - } 502 - 503 - // Second pass: create writable variants of parent schemas and update their discriminator mappings 504 - const parentSchemaVariants = new Map<string, { read?: string; write?: string }>(); 505 - 506 - for (const [parentRef, contexts] of parentSchemasToSplit) { 507 - const schemasObj = getSchemasObject(spec); 508 - if (!schemasObj) continue; 509 - 510 - const refPath = jsonPointerToPath(parentRef); 511 - const parentName = refPath[refPath.length - 1]; 512 - 513 - if (typeof parentName !== 'string' || !(parentName in schemasObj)) continue; 514 - 515 - const parentSchema = schemasObj[parentName]; 516 - if (!parentSchema || typeof parentSchema !== 'object') continue; 517 - 518 - const variants: { read?: string; write?: string } = {}; 519 - 520 - // Create variants for each context 521 - for (const context of contexts) { 522 - const variantSchema = deepClone(parentSchema); 523 - 524 - // Update discriminator mapping in the variant 525 - if ( 526 - 'discriminator' in variantSchema && 527 - variantSchema.discriminator && 528 - typeof variantSchema.discriminator === 'object' && 529 - 'mapping' in variantSchema.discriminator && 530 - variantSchema.discriminator.mapping && 531 - typeof variantSchema.discriminator.mapping === 'object' 532 - ) { 533 - const mapping = (variantSchema.discriminator as any).mapping as Record<string, string>; 534 - const updatedMapping: Record<string, string> = {}; 535 - 536 - for (const [discriminatorValue, originalRef] of Object.entries(mapping)) { 537 - const map = split.mapping[originalRef]; 538 - if (map) { 539 - if (context === 'read' && map.read) { 540 - updatedMapping[discriminatorValue] = map.read; 541 - } else if (context === 'write' && map.write) { 542 - updatedMapping[discriminatorValue] = map.write; 543 - } else { 544 - updatedMapping[discriminatorValue] = originalRef; 545 - } 546 - } else { 547 - updatedMapping[discriminatorValue] = originalRef; 548 - } 549 - } 550 - 551 - (variantSchema as any).discriminator.mapping = updatedMapping; 552 - } 553 - 554 - // Add the variant to split.schemas with an appropriate name 555 - if (context === 'write') { 556 - const variantBase = applyNaming(parentName, config.requests); 557 - const variantName = 558 - variantBase === parentName 559 - ? `${parentName}Writable` 560 - : getUniqueComponentName({ 561 - base: variantBase, 562 - components: existingNames, 563 - }); 564 - existingNames.add(variantName); 565 - split.schemas[variantName] = variantSchema; 566 - variants.write = `${schemasPointerNamespace}${variantName}`; 567 - } 568 - // We could create read variants too, but typically they're not needed 569 - // since the original schema serves as the read variant 570 - } 571 - 572 - parentSchemaVariants.set(parentRef, variants); 573 - } 574 - 575 - // Third pass: update $refs in split schemas to point to the parent variants 576 - for (const [name, schema] of Object.entries(split.schemas)) { 577 - const pointer = `${schemasPointerNamespace}${name}`; 578 - const originalPointer = split.reverseMapping[pointer]; 579 - 580 - if (originalPointer) { 581 - const mapping = split.mapping[originalPointer]; 582 - if (mapping) { 583 - const contextVariant = 584 - mapping.read === pointer ? 'read' : mapping.write === pointer ? 'write' : null; 585 - 586 - if (contextVariant && schema && typeof schema === 'object') { 587 - // Update $refs in allOf 588 - if ('allOf' in schema && Array.isArray((schema as Record<string, unknown>).allOf)) { 589 - const allOf = (schema as Record<string, unknown>).allOf as Array<unknown>; 590 - 591 - for (let i = 0; i < allOf.length; i++) { 592 - const comp = allOf[i]; 593 - 594 - if ( 595 - comp && 596 - typeof comp === 'object' && 597 - '$ref' in comp && 598 - typeof comp.$ref === 'string' 599 - ) { 600 - const ref = comp.$ref as string; 601 - const variants = parentSchemaVariants.get(ref); 602 - 603 - if (variants) { 604 - if (contextVariant === 'write' && variants.write) { 605 - (comp as any).$ref = variants.write; 606 - } else if (contextVariant === 'read' && variants.read) { 607 - (comp as any).$ref = variants.read; 608 - } 609 - } 610 - } 611 - } 612 - } 613 - } 614 - } 615 - } 616 - } 622 + splitDiscriminatorSchemas({ 623 + config, 624 + existingNames, 625 + schemasPointerNamespace, 626 + spec, 627 + split, 628 + }); 617 629 618 630 event.timeEnd(); 619 631 return split;