import { assertEquals } from "@std/assert"; import type { CELL_RESULT_TYPE, Stream } from "@commonfabric/api"; // Value-position guards, not `MustBeTrue<...>` type aliases. `MustBeTrue` does NOT catch a failed assertion: a failing // AssertAssignable/AssertNotAssignable evaluates to `never`, and `never extends // true` is true, so the alias compiles and the assertion is vacuous. Assigning // `true` to the result fails when it is `never`, which is what makes these bite. // (Verified: `MustBeTrue>` compiles clean.) type AssertAssignable = [T] extends [U] ? true : never; type AssertNotAssignable = [T] extends [U] ? never : true; interface AddTopic { title: string; } interface TopicRef { id: string; } // A verb that declares nothing is the common shape and keeps its old spelling. type ValueLess = Stream; type ExplicitVoid = Stream; type Returning = Stream; // `R` defaults to void, so every existing `Stream` means what it always did. const _DefaultIsVoid: AssertAssignable = true; const _VoidIsDefault: AssertAssignable = true; // A verb that declares a result and one that declares none are different types // in both directions, so a declared result cannot be dropped on assignment. // // These assertions alone do NOT prove the CELL_RESULT_TYPE property is doing // the work: `ICreatable>` puts the stream in `for()`'s return // position, which discriminates on `R` too. They would still pass with the // property removed. The guard below is what pins it locally. const _ReturningIsNotValueLess: AssertNotAssignable = true; const _ValueLessIsNotReturning: AssertNotAssignable = true; // Two different declared results do not interchange either. const _ResultsDiscriminate: AssertNotAssignable< Returning, Stream > = true; // A stream is still identical to itself. const _SelfAssignable: AssertAssignable = true; // The event type keeps discriminating independently of the result. const _EventStillDiscriminates: AssertNotAssignable< Returning, Stream<{ different: number }, TopicRef> > = true; // `R` is recoverable by inference — what the transformer and schema generator // read to lower a value-returning verb. type ResultOf = S extends Stream ? R : never; const _InfersDeclaredResult: AssertAssignable, TopicRef> = true; const _InfersVoidWhenUndeclared: AssertAssignable, void> = true; // The event type stays inferable alongside it. type EventOf = S extends Stream ? E : never; const _InfersEvent: AssertAssignable, AddTopic> = true; // Stream carries the result in its OWN property rather than borrowing the // discrimination from ICreatable. Deleting `[CELL_RESULT_TYPE]` from Stream // makes this line a type error, which is the point: without it the behavior // above rides on ICreatable's signature and would vanish silently if that // signature ever changed. const _ResultPinnedLocally: AssertAssignable< Returning[typeof CELL_RESULT_TYPE], TopicRef > = true; const _ValueLessPinnedLocally: AssertAssignable< ValueLess[typeof CELL_RESULT_TYPE], void > = true; // Schema compatibility checks results as candidate ⊆ previous, and "results may // narrow freely" governs values, never named fields: removing a named property // is rejected outright, at ANY depth — a nested removal reports // `result.topic.title` just as a flat one reports `result.title`. So nesting // does not reduce what a result commits to; every name it publishes is // permanent wherever it sits, and later additions must be optional. // // The two shapes below therefore differ in how many names they commit, not in // how evolvable those names are. interface AddTopicResult { topic: { fid: string; title: string }; } type EnvelopedVerb = Stream; // The enveloped form commits one top-level name — but `fid` and `title` under // it are equally permanent, so this is a smaller surface at the top only. const _EnvelopeCommitsOneTopName: AssertAssignable< keyof ResultOf, "topic" > = true; // Spread flat, the same two names are committed at the top instead of one. type FlatVerb = Stream; const _FlatCommitsEveryName: AssertAssignable< keyof ResultOf, "fid" | "title" > = true; // The two are not interchangeable, so reshaping a result after the fact is a // compile error rather than a quiet schema break. const _EnvelopeIsNotFlat: AssertNotAssignable = true; Deno.test("Stream result type parameter is structural", () => { // The assertions above are compile-time; this keeps the module a test file // and fails loudly if it ever stops being type-checked. assertEquals(typeof "compile-time", "string"); });