@@ -1279,11 +1279,13 @@ export abstract class ViewModel<
12791279 }
12801280 }
12811281
1282- constructor (
1283- // The following MUST be declared in the constructor so its value will be available to property initializers.
1282+ /** The metadata representing the type of data that this ViewModel handles. */
1283+ public declare readonly $metadata : TModel [ "$metadata" ] ;
12841284
1285- /** The metadata representing the type of data that this ViewModel handles. */
1286- public readonly $metadata : TModel [ "$metadata" ] ,
1285+ constructor (
1286+ // Metadata is set on the prototype by `defineProps`.
1287+ // This parameter only exists for backwards-compat.
1288+ $metadata : TModel [ "$metadata" ] ,
12871289
12881290 /** Instance of an API client for the model through which direct, stateless API requests may be made. */
12891291 public readonly $apiClient : TApi ,
@@ -1346,6 +1348,11 @@ export abstract class ViewModel<
13461348}
13471349
13481350const $loadProxy = Symbol ( "$loadProxy" ) ;
1351+ const $protoVersion = Symbol ( "$protoVersion" ) ;
1352+ /** Create a class that represents an abstract model type
1353+ * and will turn itself into the proper concrete ViewModel type
1354+ * when loaded.
1355+ */
13491356export function createAbstractProxyViewModelType <
13501357 TModel extends Model < ModelType > ,
13511358 TViewModel extends ViewModel
@@ -1355,95 +1362,115 @@ export function createAbstractProxyViewModelType<
13551362) : {
13561363 new ( initialData ?: DeepPartial < TModel > | null ) : TViewModel ;
13571364} {
1358- return function abstractProxy ( initialData ?: DeepPartial < any > | null ) {
1359- const apiClient = new apiClientCtor ( ) ;
1360-
1361- if ( initialData && "$metadata" in initialData ) {
1362- return ViewModelFactory . get (
1363- initialData . $metadata ! . name ! ,
1364- initialData ,
1365- true
1366- ) ;
1367- }
1368-
1369- function unsupportedError ( ) {
1370- return new Error ( `"Operation not supported: This ViewModel instance is a proxy for an abstract type, with its concrete implementation not yet decided.
1365+ function unsupportedError ( ) {
1366+ return new Error ( `"Operation not supported: This ViewModel instance is a proxy for an abstract type, with its concrete implementation not yet decided.
13711367
13721368 Try one of the following to obtain a concrete implementation:
13731369 - $load(...) or $loadFromModel(...) data for a concrete implementation
13741370 - Instantiate a concrete implementation of this abstract type instead of this abstract proxy
13751371 ` ) ;
1372+ }
1373+
1374+ class AbstractVmProxy extends ViewModel < TModel , ModelApiClient < TModel > > {
1375+ [ $loadProxy ] ?: ItemApiState < any , any , any > ;
1376+ [ $protoVersion ] ? = ref ( 0 ) ;
1377+
1378+ override get $load ( ) {
1379+ return this [ $loadProxy ] ! ;
1380+ }
1381+ override get $save ( ) {
1382+ return this . $apiClient . $makeCaller ( "item" , ( c ) => {
1383+ throw unsupportedError ( ) ;
1384+ } ) as any ;
13761385 }
1377- class AbstractVmProxy extends ViewModel {
1378- constructor ( initialDirtyData ?: any ) {
1379- super ( metadata , apiClient , initialDirtyData ) ;
1386+ override get $bulkSave ( ) {
1387+ return this . $apiClient . $makeCaller ( "item" , ( c ) => {
1388+ throw unsupportedError ( ) ;
1389+ } ) as any ;
1390+ }
1391+ override get $delete ( ) {
1392+ return this . $apiClient . $makeCaller ( "item" , ( c ) => {
1393+ throw unsupportedError ( ) ;
1394+ } ) as any ;
1395+ }
1396+
1397+ constructor ( initialDirtyData ?: any ) {
1398+ if (
1399+ initialDirtyData &&
1400+ "$metadata" in initialDirtyData &&
1401+ ViewModel . typeLookup ?. [ initialDirtyData . $metadata . name ]
1402+ ) {
1403+ // We know the real concrete type of this ViewModel from the metadata.
1404+ // Return the proper instance directly, bypassing the conversion process.
1405+ return ViewModelFactory . get (
1406+ initialDirtyData . $metadata . name ,
1407+ initialDirtyData ,
1408+ false
1409+ ) as any ;
13801410 }
13811411
1382- [ $loadProxy ] ! : ItemApiState < any , any , any > ;
1412+ super ( metadata , new apiClientCtor ( ) , initialDirtyData ) ;
13831413
1384- override get $load ( ) {
1385- return this [ $loadProxy ] ;
1386- }
1387- override get $save ( ) {
1388- return apiClient . $makeCaller ( "item" , ( c ) => {
1389- throw unsupportedError ( ) ;
1390- } ) ;
1391- }
1392- override get $bulkSave ( ) {
1393- return apiClient . $makeCaller ( "item" , ( c ) => {
1394- throw unsupportedError ( ) ;
1395- } ) ;
1396- }
1397- override get $delete ( ) {
1398- return apiClient . $makeCaller ( "item" , ( c ) => {
1399- throw unsupportedError ( ) ;
1400- } ) ;
1401- }
1402- }
1403- Object . defineProperty ( AbstractVmProxy , "name" , {
1404- value : metadata . name + "ViewModelProxy" ,
1405- } ) ;
1406- defineProps ( AbstractVmProxy , metadata ) ;
1414+ const vm = this ;
14071415
1408- let vm = new AbstractVmProxy ( initialData ) ;
1416+ let $load = ( vm [ $loadProxy ] = vm . $apiClient
1417+ . $makeCaller ( "item" , ( c , id ?: any ) => {
1418+ return c . get ( id != null ? id : vm . $primaryKey , vm . $params ) ;
1419+ } )
1420+ . onFulfilled ( ( state ) => {
1421+ const result = state . result ! ;
14091422
1410- let $load = ( vm [ $loadProxy ] = apiClient
1411- . $makeCaller ( "item" , ( c , id ?: any ) => {
1412- return c . get ( id != null ? id : vm . $primaryKey , vm . $params ) ;
1413- } )
1414- . onFulfilled ( ( state ) => {
1415- const result = state . result ;
1423+ var realCtor = ViewModel . typeLookup ! [ result . $metadata . name ] ;
14161424
1417- var realCtor = ViewModel . typeLookup ! [ result ! . $metadata . name ] ;
1425+ // Grab real metadata and API client instances of the concrete type.
1426+ const { $apiClient, $metadata } = new realCtor ( ) ;
14181427
1419- // Grab real metadata and API client instances of the concrete type.
1420- const { $apiClient, $metadata } = new realCtor ( ) ;
1428+ // Update the tracking ref for prototype version so that instanceof is reactive.
1429+ vm [ $protoVersion ] ! . value ++ ;
1430+ delete vm [ $protoVersion ] ;
14211431
1422- // Convert the ViewModel instance to the target type:
1423- Object . setPrototypeOf ( vm , realCtor . prototype ) ;
1424- //@ts -expect-error normally readonly.
1425- vm . $metadata = $metadata ;
1432+ // Convert the ViewModel instance to the target type:
1433+ Object . setPrototypeOf ( vm , realCtor . prototype ) ;
14261434
1427- // Convert the ApiClient instance to the target type:
1428- Object . setPrototypeOf ( vm . $apiClient , Object . getPrototypeOf ( $apiClient ) ) ;
1429- vm . $apiClient . $metadata = $metadata ;
1435+ // Convert the ApiClient instance to the target type:
1436+ Object . setPrototypeOf (
1437+ vm . $apiClient ,
1438+ Object . getPrototypeOf ( $apiClient )
1439+ ) ;
1440+ vm . $apiClient . $metadata = $metadata ;
14301441
1431- // Populate the properties of the $load caller on the real $load caller instance.
1432- //@ts -expect-error protected prop or fn
1433- vm . $load . setResponseProps ( $load . rawResponse . data ) ;
1434- //@ts -expect-error protected prop or fn
1435- vm . $load . __rawResponse . value = $load . rawResponse ;
1436- vm . $load . isLoading = false ;
1437- vm . $load . concurrencyMode = $load . concurrencyMode ;
1442+ // Populate the properties of the $load caller on the real $load caller instance.
1443+ //@ts -expect-error protected prop or fn
1444+ vm . $load . setResponseProps ( $load . rawResponse . data ) ;
1445+ //@ts -expect-error protected prop or fn
1446+ vm . $load . __rawResponse . value = $load . rawResponse ;
1447+ vm . $load . isLoading = false ;
1448+ vm . $load . concurrencyMode = $load . concurrencyMode ;
14381449
1439- //@ts -expect-error cleaning up for GC
1440- vm [ $loadProxy ] = $load = undefined ;
1450+ //@ts -expect-error cleaning up for GC
1451+ $load = undefined ;
1452+ delete vm [ $loadProxy ] ;
14411453
1442- vm . $loadCleanData ( result ) ;
1443- } ) ) ;
1454+ vm . $loadCleanData ( result ) ;
1455+ } ) ) ;
1456+ }
1457+ }
14441458
1445- return vm ;
1446- } as any ;
1459+ Object . defineProperty ( AbstractVmProxy , "name" , {
1460+ value : metadata . name + "ViewModelProxy" ,
1461+ } ) ;
1462+
1463+ defineProps ( AbstractVmProxy , metadata ) ;
1464+
1465+ // Make `$metadata` reactive so components depending on it update when it changes.
1466+ Object . defineProperty ( AbstractVmProxy . prototype , "$metadata" , {
1467+ get ( ) {
1468+ this [ $protoVersion ] ?. value . toString ( ) ;
1469+ return metadata ;
1470+ } ,
1471+ } ) ;
1472+
1473+ return AbstractVmProxy as any ;
14471474}
14481475
14491476export interface BulkSaveRequestRawItem {
@@ -2230,6 +2257,25 @@ export function defineProps<T extends new () => ViewModel>(
22302257) {
22312258 const props = Object . values ( metadata . props ) ;
22322259 const descriptors = { } as PropertyDescriptorMap ;
2260+
2261+ if ( metadata . baseTypes ?. some ( ( base ) => base . abstract ) ) {
2262+ // Make the `instanceof` operator against this type reactive if the type
2263+ // has a known abstract base class such that an instance could change types via AbstractVmProxy
2264+ Object . defineProperty ( ctor , Symbol . hasInstance , {
2265+ value ( x : any ) {
2266+ // Take a dependency, tracked per instance, that will update when the instance changes type.
2267+ x [ $protoVersion ] ?. value . toString ( ) ;
2268+ return Object [ Symbol . hasInstance ] . call ( this , x ) ;
2269+ } ,
2270+ } ) ;
2271+ }
2272+
2273+ descriptors [ "$metadata" ] = {
2274+ enumerable : true ,
2275+ configurable : true ,
2276+ value : metadata ,
2277+ } ;
2278+
22332279 for ( let i = 0 ; i < props . length ; i ++ ) {
22342280 const prop = props [ i ] ;
22352281 const propName = prop . name ;
0 commit comments