type-bridge-schema-codegen 2.2.2

Deterministic projection-only schema emitters for type-bridge
Documentation
use std::collections::BTreeSet;

use type_bridge_contract::fingerprint::SemanticProfileId;
use type_bridge_contract::projection::{BindingTarget, CodeResourceDigest, ProjectionConfig};
use type_bridge_contract::schema::DocumentId;
use type_bridge_schema::{SchemaDocumentSet, normalize_documents, project, resolve};
use type_bridge_schema_codegen::TypeScriptEmitter;

mod support;

fn projected(
    source: &str,
    resources: &[CodeResourceDigest],
) -> (
    type_bridge_contract::projection::RuntimeProjection,
    type_bridge_schema::VerifiedSchemaAuthority,
) {
    projected_with_config(source, resources, &ProjectionConfig::typescript())
}

fn projected_with_config(
    source: &str,
    resources: &[CodeResourceDigest],
    config: &ProjectionConfig,
) -> (
    type_bridge_contract::projection::RuntimeProjection,
    type_bridge_schema::VerifiedSchemaAuthority,
) {
    let documents =
        SchemaDocumentSet::parse([(DocumentId::new("typescript-emitter.yaml").unwrap(), source)])
            .unwrap();
    let declared = normalize_documents(&documents).unwrap();
    let profile = SemanticProfileId::new("typedb-3.12.1/v1").unwrap();
    let resolved = resolve(&declared, &profile).unwrap();
    let projection = project(
        &resolved,
        BindingTarget::TypeScript,
        config,
        &TypeScriptEmitter::new().generator_handlers(),
        resources,
    )
    .unwrap();
    (projection, support::authority(source))
}

#[test]
fn emits_exact_deterministic_es_module_package() {
    let emitter = TypeScriptEmitter::new();
    let resources = emitter.code_resources().unwrap();
    let (projection, authority) = projected(include_str!("acceptance/schema.yaml"), &resources);
    let first = emitter.emit(&projection, &authority).unwrap();
    let second = emitter.emit(&projection, &authority).unwrap();
    assert_eq!(first, second);
    assert_eq!(
        first
            .files()
            .keys()
            .map(String::as_str)
            .collect::<BTreeSet<_>>(),
        BTreeSet::from([
            "package.json",
            "src/authority.ts",
            "src/functions.ts",
            "src/index.ts",
            "src/models.ts",
            "src/node-fs.d.ts",
            "src/runtime.ts",
            "src/schema.ts",
            "src/structs.ts",
            "tsconfig.json",
            "typebridge/migration-history.json",
        ])
    );
    let models = String::from_utf8(first.get("src/models.ts").unwrap().to_vec()).unwrap();
    assert!(
        models.find("export let Person:").unwrap() < models.find("Person = defineModel").unwrap()
    );
    assert!(models.contains("Link one dependency component"));
    assert!(models.contains("readonly value: string;"));
    assert!(models.contains("export type IdentifierCreate = string;"));
    assert!(models.contains("export type ScoreCreate = bigint;"));
    assert!(models.contains("valueType: \"long\","));
    let runtime = String::from_utf8(first.get("src/runtime.ts").unwrap().to_vec()).unwrap();
    assert!(runtime.contains("readonly iid: string | null;"));
    assert!(runtime.contains("HYDRATE_COMPLETE_BRAND"));
    assert!(runtime.contains("ProjectedModelManager"));
    let index = String::from_utf8(first.get("src/index.ts").unwrap().to_vec()).unwrap();
    assert!(index.contains("__installRuntimeProjectionPackage"));
    assert!(index.contains("export function openMigrationCatalog(): MigrationCatalog"));
    assert!(index.contains("../${MIGRATION_HISTORY_RESOURCE}"));
    assert!(index.contains("RUNTIME_PROJECTION_JSON"));
    assert!(runtime.contains("schemaAuthorityJson,"));
    assert!(!index.contains("export * from \"./authority.js\""));
    let authority_source =
        String::from_utf8(first.get("src/authority.ts").unwrap().to_vec()).unwrap();
    assert!(authority_source.contains("typebridge.schema-authority/v1"));
    assert!(!authority_source.contains("DECLARED_SCHEMA_JSON"));
    assert!(!authority_source.contains("MANAGED_SCOPE_ID"));
    assert!(!authority_source.contains("SEMANTIC_PROFILE_ID"));
    let schema = String::from_utf8(first.get("src/schema.ts").unwrap().to_vec()).unwrap();
    assert!(schema.contains("export const playsPersonMembershipMember"));
    assert!(schema.contains("export const playsRobotMembershipMember"));
    assert_ne!(
        schema.find("export const playsPersonMembershipMember"),
        schema.find("export const playsRobotMembershipMember")
    );
    let package_json = String::from_utf8(first.get("package.json").unwrap().to_vec()).unwrap();
    assert!(package_json.contains("\"type\": \"module\""));
    assert!(package_json.contains("\"@type-bridge/node\": \"^2.2.2\""));
    assert!(
        String::from_utf8(first.get("tsconfig.json").unwrap().to_vec())
            .unwrap()
            .contains("\"module\": \"NodeNext\"")
    );
}

#[test]
fn emits_canonical_struct_type_identity() {
    let source = include_str!("../../../../tests/contracts/sdk_conformance/sdk-v3/schema-v3.yaml");
    let emitter = TypeScriptEmitter::new();
    let documents =
        SchemaDocumentSet::parse([(DocumentId::new("typescript-struct.yaml").unwrap(), source)])
            .unwrap();
    let declared = normalize_documents(&documents).unwrap();
    let resolved = resolve(
        &declared,
        &SemanticProfileId::new("typedb-3.12.1/v1").unwrap(),
    )
    .unwrap();
    let resources = emitter.code_resources_for(&resolved).unwrap();
    let projection = project(
        &resolved,
        BindingTarget::TypeScript,
        &ProjectionConfig::typescript(),
        &emitter.generator_handlers_for(&resolved),
        &resources,
    )
    .unwrap();
    let authority = support::authority(source);
    let package = emitter.emit(&projection, &authority).unwrap();
    let structs = String::from_utf8(package.get("src/structs.ts").unwrap().to_vec()).unwrap();
    assert!(structs.contains(r#"id: "{\"kind\":\"struct\",\"label\":\"player-stats\"}""#));
}

#[test]
fn emits_safely_escaped_type_and_direct_sub_documentation() {
    let emitter = TypeScriptEmitter::new();
    let resources = emitter.code_resources().unwrap();
    let (projection, authority) = projected(
        r#"format: typebridge.schema/v2
entities:
  actor: {}
  person:
    doc: |-
      Type "doc".
      closing */ kept
    sub:
      type: actor
      doc: |-
        Edge 'doc' \ path
        closes */ safely
"#,
        &resources,
    );
    let package = emitter.emit(&projection, &authority).unwrap();
    let models = std::str::from_utf8(package.get("src/models.ts").unwrap()).unwrap();
    let documentation = "/**\n * Type \"doc\".\n * closing *\\/ kept\n * \n * Direct subtype of `actor`:\n * Edge 'doc' \\ path\n * closes *\\/ safely\n */\n";

    assert!(models.contains(&format!(
        "{documentation}export interface Person extends CompleteFacet<"
    )));
    assert!(models.contains(&format!(
        "{documentation}export interface PersonRef extends ReferenceFacet<"
    )));
    assert!(!models.contains("closing */ kept\n *"));
}

#[test]
fn rejects_projection_without_exact_resource_evidence() {
    let (projection, authority) = projected(include_str!("acceptance/schema.yaml"), &[]);
    let error = TypeScriptEmitter::new()
        .emit(&projection, &authority)
        .unwrap_err();
    assert!(
        error
            .to_string()
            .contains("typescript_emitter_evidence_mismatch")
    );
}

#[test]
fn rejects_schema_name_colliding_with_runtime_export() {
    let emitter = TypeScriptEmitter::new();
    let resources = emitter.code_resources().unwrap();
    let (projection, authority) = projected(
        "format: typebridge.schema/v2\nentities:\n  cardinality: {}\n",
        &resources,
    );
    let error = emitter.emit(&projection, &authority).unwrap_err();
    assert!(
        error
            .to_string()
            .contains("typescript_emitter_name_collision")
    );
    assert!(error.to_string().contains("Cardinality"));
}

#[test]
fn emits_collision_safe_members_with_canonical_key_identity() {
    use type_bridge_contract::id::{AttributeId, TypeId, TypeKind};
    use type_bridge_contract::schema::OwnsFactId;

    let emitter = TypeScriptEmitter::new();
    let resources = emitter.code_resources().unwrap();
    let source = include_str!("typescript_acceptance/schema-blockers.yaml");
    let config = ProjectionConfig::typescript()
        .with_type_name_override(
            TypeId::new(TypeKind::Attribute, "powertrain_ref").unwrap(),
            "PowertrainReferenceValue",
        )
        .unwrap();
    let (projection, authority) = projected_with_config(source, &resources, &config);
    let marker = TypeId::new(TypeKind::Entity, "marker").unwrap();
    let key = OwnsFactId::new(marker.clone(), AttributeId::new("name").unwrap()).unwrap();
    let model = &projection.models()[&marker];
    assert_eq!(
        model.reference_read().key_fields(),
        std::slice::from_ref(&key)
    );
    let token = &model.query_tokens().fields()[&key];
    assert!(token.is_key());
    assert_eq!(token.target_name().as_str(), "name_");
    assert!(
        model
            .create()
            .fields()
            .iter()
            .any(|field| field.token() == &key)
    );
    let package = emitter.emit(&projection, &authority).unwrap();
    let models = std::str::from_utf8(package.get("src/models.ts").unwrap()).unwrap();
    assert!(models.contains("readonly name_:"));
    assert!(models.contains("readonly nameValue:"));
    assert_eq!(package, emitter.emit(&projection, &authority).unwrap());
}

#[test]
fn emits_type_name_overrides_without_changing_database_tokens() {
    use type_bridge_contract::id::{TypeId, TypeKind};
    let source = "format: typebridge.schema/v2\nattributes:\n  powertrain_ref: { value: string }\nentities:\n  Powertrain:\n    owns:\n      powertrain_ref: { card: 1 }\n";
    let emitter = TypeScriptEmitter::new();
    let authority = support::authority(source);
    let resolved = authority.resolved_schema();
    let config = ProjectionConfig::typescript()
        .with_type_name_override(
            TypeId::new(TypeKind::Attribute, "powertrain_ref").unwrap(),
            "PowertrainReferenceValue",
        )
        .unwrap();
    let projection = project(
        resolved,
        BindingTarget::TypeScript,
        &config,
        &emitter.generator_handlers_for(resolved),
        &emitter.code_resources_for(resolved).unwrap(),
    )
    .unwrap();
    let package = emitter.emit(&projection, &authority).unwrap();
    let models = std::str::from_utf8(package.get("src/models.ts").unwrap()).unwrap();
    assert!(models.contains("PowertrainReferenceValue"));
    assert!(models.contains("PowertrainRef"));
    assert!(models.contains("powertrain_ref"));
    assert_eq!(emitter.emit(&projection, &authority).unwrap(), package);
}