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);
}